Comparing version 2.1.0-beta2 to 2.1.0
13
clues.js
@@ -26,3 +26,3 @@ // clues.js (c) 2009-2014 Ziggy.jonsson.nyc@gmail.com @license MIT | ||
function clues(logic,facts) { | ||
function clues(logic,facts,options) { | ||
if (!(this instanceof clues)) | ||
@@ -32,2 +32,3 @@ return new clues(logic,facts); | ||
this.facts = facts || {}; | ||
this.options = options || {}; | ||
this.self = this; | ||
@@ -50,6 +51,8 @@ } | ||
if (self.logic[ref] === undefined) { | ||
if (local[ref] !== undefined) return self.Promise.fulfilled(local[ref]); | ||
else if (self[ref] !== undefined && typeof self[ref] !== 'function') return self.Promise.fulfilled(self[ref]); | ||
else if (ref === 'local') return self.Promise.fulfilled(local); | ||
else return self.Promise.rejected({ref: ref, caller: caller, message: ref+' not defined', name: 'Undefined'}); | ||
if (caller !== '__user__') { | ||
if (local[ref] !== undefined) return self.Promise.fulfilled(local[ref]); | ||
if (self[ref] !== undefined && typeof self[ref] !== 'function') return self.Promise.fulfilled(self[ref]); | ||
} | ||
if (typeof(self.options.fallback) === 'function') return self.facts[ref] = self.options.fallback.call(this,ref,local,caller); | ||
return self.Promise.rejected({ref: ref, caller: caller, message: ref+' not defined', name: 'Undefined'}); | ||
} | ||
@@ -56,0 +59,0 @@ |
{ | ||
"name": "clues", | ||
"version": "2.1.0-beta2", | ||
"version": "2.1.0", | ||
"description": "Lightweight logic tree solver using promises.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -25,3 +25,3 @@ ##Breaking changes in version 2.0: | ||
### `clues([logic],[facts])` | ||
### `clues([logic],[facts],[options])` | ||
Creates a new clues object based on a particular logic (set of functions and values by reference) and facts (associative arrays of known values). Logic and fact objects can be defined/modified later as they are public variables of the clues object. | ||
@@ -33,2 +33,4 @@ | ||
An optional fallback function can be defined in the options object. This function will be called whenever a reference can not be found in current fact/logic space and must return a promise. | ||
### `clues.logic = {}` | ||
@@ -35,0 +37,0 @@ Key/value dictionary of logic functions and/or values. Functions in the logic object must only contain arguments names that correspond to either facts or as other logic functions/values. Each logic function must either return a value or a promise. |
@@ -6,3 +6,3 @@ var clues = require("../clues"), | ||
var f = {a: 1, b: 2}, | ||
l = {c: 3, d: 4, l : function(local) { return local; }}, | ||
l = {c: 3, d: 4, l : function(a,b) { return {a: a,b: b}; }}, | ||
c = clues(l,f); | ||
@@ -9,0 +9,0 @@ |
var clues = require('../clues'), | ||
Promise = require('bluebird'); | ||
function stringifyError(e) { | ||
return { | ||
message : e.message, | ||
ref : e.ref, | ||
caller : e.caller, | ||
stack : e.stack, | ||
error : true | ||
}; | ||
} | ||
module.exports = function(api,options) { | ||
api = api || {}; | ||
options = options || {}; | ||
function stringifyError(e) { | ||
var message = { | ||
message : (!options.debug && e.stack) ? 'Internal Error' : e.message, | ||
ref : e.ref, | ||
error : true | ||
}; | ||
function multi(data,self,res,req) { | ||
res.setHeader('content-type','application/ocetstream'); | ||
res.write('{\n\t"multi":true\t\n'); | ||
if (options.debug) { | ||
message.stack = e.stack; | ||
message.caller = e.caller; | ||
} | ||
data = data.split(',') | ||
.map(function(ref) { | ||
return self.solve(ref) | ||
.catch(stringifyError) | ||
.then(function(d) { | ||
res.write(', "'+ref+'" : '+JSON.stringify(d)+'\t\n'); | ||
}); | ||
}); | ||
return message; | ||
} | ||
req.on('close',function() { | ||
data.forEach(function(d) { | ||
d.cancel(); | ||
}); | ||
}); | ||
return function(req,res) { | ||
req.body = req.body || {}; | ||
res.set('Transfer-Encoding','chunked'); | ||
res.set('Content-Type', 'application/json; charset=UTF-8'); | ||
res.set('Cache-Control', 'no-cache, no-store, max-age=0'); | ||
res.write('{ \t\n\n'); | ||
if (typeof(res.flush) == 'function') res.flush(); | ||
return Promise.all(data) | ||
.then(function(d) { | ||
res.write('}'); | ||
res.end(); | ||
}); | ||
} | ||
Object.keys(req.query || {}) | ||
.forEach(function(key) { | ||
req.body[key] = req.query[key]; | ||
}); | ||
if (options.safe) { | ||
Object.keys(req.body).forEach(function(key) { | ||
if (api[key]) delete req.body[key]; | ||
}); | ||
} | ||
function help(self) { | ||
return Object.keys(self.logic); | ||
} | ||
var c = clues(api,req.body); | ||
module.exports = function(api) { | ||
api = api || {}; | ||
api.multi = multi; | ||
api.help = help; | ||
var data = req.param("fn") | ||
.split(',') | ||
.map(function(ref) { | ||
return c.solve(ref,{res:res,req:req},'__user__') | ||
.catch(stringifyError) | ||
.then(function(d) { | ||
res.write(' "'+ref+'" : '+JSON.stringify(d)+',\t\n'); | ||
if (typeof(res.flush) == 'function') res.flush(); | ||
}); | ||
}); | ||
return function(req,res) { | ||
res.set('Content-Type','application/json'); | ||
clues(api,req.query) | ||
.solve(req.param("fn"),{req:req,res:res}) | ||
.catch(stringifyError) | ||
req.on('close',function() { | ||
data.forEach(function(d) { | ||
d.cancel(); | ||
}); | ||
}); | ||
return Promise.all(data) | ||
.then(function(d) { | ||
if (d && !d.error && req.param('select')) { | ||
req.param('select') | ||
.split('.') | ||
.forEach(function(key) { | ||
d = d && d[key]; | ||
}); | ||
} | ||
res.end(JSON.stringify(d,null,2)); | ||
res.write(' "__end__" : true\t\n}'); | ||
res.end(); | ||
}); | ||
}; | ||
}; |
1471
util/reptile.js
/* global angular, self, d3, dpl, Promise */ | ||
//start | ||
(function() { | ||
var UNIQUE_ID=0; | ||
var weight = 0.4; | ||
var reptile = {widgets:{}}; | ||
var reptile = self.reptile = {widgets:{}}; | ||
var reArgs = /function.*?\((.*?)\).*/; | ||
function matchArgs(fn) { | ||
if (fn.__args__) return fn.__args__; | ||
var match = reArgs.exec(fn.prototype.constructor.toString()); | ||
return fn.__args__ = match[1].replace(/\s/g,'').split(","); | ||
} | ||
angular.module('reptile',[]); | ||
Promise.onPossiblyUnhandledRejection(function(){}); | ||
/* global reptile,self,report*/ | ||
var reArgs = /function.*?\((.*?)\).*/; | ||
function matchArgs(fn) { | ||
if (fn.__args__) return fn.__args__; | ||
var match = reArgs.exec(fn.prototype.constructor.toString()); | ||
return fn.__args__ = match[1].replace(/\s/g,'').split(","); | ||
} | ||
function serialize(obj) { | ||
var str = []; | ||
for(var p in obj) | ||
if (obj.hasOwnProperty(p)) { | ||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); | ||
} | ||
return str.join("&"); | ||
} | ||
/* global Promise,d3*/ | ||
function serialize(obj) { | ||
var str = []; | ||
for(var p in obj) | ||
if (obj.hasOwnProperty(p)) { | ||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); | ||
} | ||
return str.join("&"); | ||
} | ||
function api(inputs,applyFn) { | ||
@@ -50,3 +19,2 @@ var queue = [], | ||
inputs = inputs || {}; | ||
@@ -69,3 +37,3 @@ | ||
var key = m[1], value = JSON.parse(m[2]); | ||
console.log(key,data) | ||
if (!data[key] || !data[key].promise) | ||
@@ -80,32 +48,24 @@ data[key] = Promise.defer(); | ||
} | ||
}) | ||
}); | ||
} | ||
var r = new XMLHttpRequest(); | ||
var buffer = '',last = 0; | ||
r.open('POST','/api/'+fns.join(','),true); | ||
r.setRequestHeader('Content-Type','application/json;charset=UTF-8'); | ||
r.send(JSON.stringify(inputs)); | ||
r.onprogress = function(d) { | ||
if (r.responseText.length) buffer += r.responseText.slice(last); | ||
var items = buffer.split('\t\n'); | ||
processItems(items.slice(0,items.length-1)); | ||
buffer = items.slice(items.length-1)[0]; | ||
last = r.responseText.length; | ||
}; | ||
var r = new XMLHttpRequest(); | ||
var buffer = "",last = 0; | ||
r.open('GET','/api/multi?data='+fns.join(',')+'&'+serialize(inputs),true); | ||
r.send() | ||
r.onprogress = function(d) { | ||
console.log(r.responseText) | ||
if (r.responseText.length) buffer += r.responseText.slice(last); | ||
//console.log('progress',buffer) | ||
var items = buffer.split('\t\n'); | ||
processItems(items.slice(0,items.length-1)); | ||
buffer = items.slice(items.length-1)[0] | ||
last = r.responseText.length | ||
} | ||
r.onload = function() { | ||
console.log(JSON.stringify(last,r.responseText)) | ||
if (r.responseText.length) buffer += r.responseText.slice(last); | ||
console.log('buffer',buffer) | ||
processItems(buffer.split('\t\n'));resolve('ok') | ||
}; | ||
r.onload = function() { | ||
if (r.responseText.length) buffer += r.responseText.slice(last); | ||
processItems(buffer.split('\t\n')); | ||
resolve('ok'); | ||
}; | ||
}) | ||
@@ -141,12 +101,10 @@ .then(function(d) { | ||
api.renderAll = function(d) { | ||
d = d3.select(d[0]).selectAll('[data-reptile]'); | ||
var items = d[0].map(function(element) { | ||
element._reptile_root = d3.select(d[0]); | ||
return api.render(element); | ||
}); | ||
var selection = (d || window).querySelectorAll('[data-reptile]'), | ||
items = [].map.call(selection,function(d) { | ||
return api.render(d); | ||
}); | ||
return Promise.settle(items); | ||
}; | ||
api.render = function(d) { | ||
var element = d3.select(d).node(); | ||
api.render = function(element) { | ||
var keys = element.dataset.reptile.split(','),key; | ||
@@ -183,13 +141,8 @@ | ||
if (keys.length) return renderKey(); | ||
d3.select(element).text(key+' error '+(e.message || e)); | ||
element.innerHTML='<p>'+key+' error '+(e.message || e); | ||
throw e; | ||
}); | ||
} | ||
return renderKey(); | ||
}; | ||
return api; | ||
@@ -200,1360 +153,2 @@ } | ||
/* global reptile, d3 */ | ||
reptile.format = { | ||
date : function(d) { return d3.time.format('%m/%d/%y')(new Date(d)); }, | ||
dround : function(d) { return "$"+d3.format(",.0f")(d); }, | ||
round : function(d) { return d3.format(",.2f")(d); }, | ||
integ : function(d) { return d3.format(",.f")(d);}, | ||
aperc : function(d) { return d3.format(",.2f")(d)+"%";}, | ||
perc : d3.format(".2%") | ||
}; | ||
/* global reptile, d3 */ | ||
// Field headers and reptile.formats | ||
function notZero(d) { | ||
return d; | ||
} | ||
function maxLen(n) { | ||
return function(d) { | ||
return d && d.slice(0,n); | ||
}; | ||
} | ||
function dqDate(d) { | ||
if (!d) return ''; | ||
d = d.toString(); | ||
return d.slice(0,4)+'/'+d.slice(4,6)+'/'+d.slice(6); | ||
} | ||
reptile.fields = { | ||
LOAN_NO : { txt: 'Loan No',fmt:function(d) { return +d;}}, | ||
POOL_ID : { txt: 'Pool no'}, | ||
CITY : {txt:'City'}, | ||
ORIG_DATE : {txt:'Orig. Date',fmt : reptile.format.date}, | ||
ORIG_AMT : {txt:'Loan Amount',fmt : reptile.format.dround}, | ||
APPRAISAL : {fmt : reptile.format.dround}, | ||
MATURITY_DATE : {txt:'Maturity',fmt: reptile.format.date}, | ||
ORIG_INTEREST : {txt:'Rate',fmt:reptile.format.aperc}, | ||
ORIG_LTV : {txt:'LTV',fmt:reptile.format.aperc}, | ||
ORIG_COMB_LTV : {txt:'Combined LTV',fmt:reptile.format.aperc}, | ||
FICO : {txt:'FICO'}, | ||
SECOND_AMT : {txt:'2nd Lien',fmt:reptile.format.dround,filter:function(d) { return d; }}, | ||
FIRST_AMT : {txt:'1st Lien',fmt:reptile.format.dround,filter:function(d) { return d; }}, | ||
// Comps | ||
DT : {txt:'Date',fmt: reptile.format.date}, | ||
DIST : {txt:'Dist',fmt: reptile.format.round}, | ||
PSQFT : {txt:'PSQ',fmt:d3.format(",.0f")}, | ||
AMT : {txt:'Price',fmt:reptile.format.dround}, | ||
PRICE : {txt:'Price',fmt:reptile.format.dround}, | ||
SA_SQFT : {txt:'SQFT',fmt:d3.format(",.0f")}, | ||
// Timeline | ||
DIST_DATE : {txt:'Date',fmt:reptile.format.date}, | ||
BALANCE : {txt:'Balance',fmt:reptile.format.dround}, | ||
INV_BALANCE : {txt:'Inv. Bal.',fmt:reptile.format.dround}, | ||
SCH_PNI : {txt:'PNI',fmt:reptile.format.dround}, | ||
RATE : {txt:'Rate',fmt:reptile.format.aperc}, | ||
LAST_MOD_RATE : {txt:'Last Mod Rate',fmt:reptile.format.aperc}, | ||
NEXT_DUE_DATE : {txt:'Due date',fmt:reptile.format.date}, | ||
DATE : {fmt:reptile.format.date}, | ||
PAYOFF_DATE : {txt:'Payoff Dt',fmt:reptile.format.date}, | ||
PAYOFF_AMT : {txt:'Payoff Amt',fmt:reptile.format.dround}, | ||
CURR_LOSS : {txt:'Loss',fmt:reptile.format.dround}, | ||
CUMUL_LOSS : {txt:'Cumul. Loss',fmt:reptile.format.dround}, | ||
CURR_BAL : {txt:'Final Bal',fmt:reptile.format.dround}, | ||
CURR_SEV : {txt:'SEV',fmt:reptile.format.perc}, | ||
MBA_STATUS : {txt:'Status'}, | ||
DLQ_STATUS : {txt:'DLQ'}, | ||
OTS : {txt:'OTS'}, | ||
EST_ADVANCE : {txt:'Est. Advance',fmt:reptile.format.dround}, | ||
CURR_DIST_DATE : {txt:'Dist Date',fmt:reptile.format.date}, | ||
// Transactions | ||
SR_BUYER : {txt:'Buyer',fmt:maxLen(15)}, | ||
SR_SELLER : {txt:'Seller',fmt:maxLen(15)}, | ||
SR_DATE_TRANSFER : {txt:'Transfer',fmt:dqDate}, | ||
SR_DATE_FILING : {txt:'Filing',fmt:dqDate}, | ||
SR_LNDR_FIRST_NAME_1 : {txt: 'Lender 1'}, | ||
SR_LOAN_VAL_1 : {txt:'Loan Amt 1',fmt:reptile.format.dround,filter:notZero}, | ||
SR_LOAN_VAL_2 : {txt:'Loan Amt 2',fmt:reptile.format.dround,filter:notZero}, | ||
SR_VAL_TRANSFER : {txt:'Transfer Amount',fmt:reptile.format.dround,filter:notZero}, | ||
MOD_DATE : {txt:'Date',fmt:reptile.format.date}, | ||
PRE_AMT : {txt:'Prior Balance',fmt:reptile.format.dround}, | ||
POST_BAL : {txt:'Modified Balance',fmt:reptile.format.dround}, | ||
PRE_INT : {txt:'Prior Interest',fmt:reptile.format.aperc}, | ||
POST_INT : {txt:'Modified Interest',fmt:reptile.format.aperc}, | ||
// Assessor | ||
ADDR : {txt:'Address'}, | ||
SA_CITY : {txt:'City'}, | ||
SA_STATE : {txt:'State'}, | ||
SA_ZIP : {txt:'Zip Code'}, | ||
SA_LOTSIZE : {txt:'Lot Size',fmt:reptile.format.dround}, | ||
SA_NBR_BATH : {txt:'# Bathrooms'}, | ||
SA_NBR_BEDRMS : {txt: '# Bedrooms'}, | ||
HOUSE_SQFT : {txt:'House Size (sqft)',fmt:reptile.format.integ}, | ||
HOUSE_ACRES : {txt:'Lot Size (acres)',fmt:reptile.format.round}, | ||
SA_VAL_ASSD : {txt:'Assessment',fmt:reptile.format.dround}, | ||
SA_TAX_VAL : {txt:'Property Tax',fmt:reptile.format.dround}, | ||
txt : {txt:'Event'}, | ||
SA_YR_BLT : {txt:'Year Built'}, | ||
USE_CODE_STD_TXT : {txt:'Property Type'}, | ||
SA_BLDG_CODE_TXT : {txt:'Structure'}, | ||
SA_EXTERIOR_1_CODE_TXT : {txt:'Exterior'}, | ||
SA_FOUNDATION_CODE_TXT : {txt:'Foundation'}, | ||
TXT : {txt:'Item'}, | ||
BLANK : {txt:' '}, | ||
AMOUNT : {txt:'Amount',fmt:reptile.format.dround}, | ||
PERC_BAL : {txt:'% Bal.',fmt:reptile.format.perc} | ||
}; | ||
// Returns the title for a given field, or a non-capitalized version with spaces, if not defined | ||
reptile.fieldName = function fieldName(field) { | ||
return (reptile.fields[field] && reptile.fields[field].txt) || (field.slice(0,1)+field.slice(1).toLowerCase()).replace(/_/g,' '); | ||
}; | ||
// Returns a value for a field, reptile.formatted correctly (if defined) | ||
reptile.value = function value(field,d) { | ||
return (d !== undefined && reptile.fields[field] && reptile.fields[field].fmt) ? reptile.fields[field].fmt(d) : d; | ||
}; | ||
// URLEncode an object | ||
function serialize(obj) { | ||
var str = []; | ||
for (var p in obj) | ||
str.push(encodeURIComponent(p)+"="+encodeURIComponent(obj[p])); | ||
return str.join("&"); | ||
} | ||
// Extract argument names from a function | ||
var reArgs = /function.*?\((.*?)\).*/; | ||
function matchArgs(fn) { | ||
var match = reArgs.exec(fn.prototype.constructor.toString()); | ||
return match && match[1].replace(/\s/g,'').split(","); | ||
} | ||
/* global reptile */ | ||
/* global reptile, d3 */ | ||
reptile.item = function item(facts) { | ||
d3.select(this).text(facts); | ||
}; | ||
/* global reptile, d3 */ | ||
// ## List | ||
// Renders input data as an itemized list | ||
reptile.list = function list(data,fields,max) { | ||
fields = fields || Object.keys(data); | ||
data = fields | ||
.filter(function(field) { | ||
if (data[field] === null || data[field] === undefined) return false; | ||
if (reptile.fields[field] && reptile.fields[field].filter && !reptile.fields[field].filter(data[field])) return false; | ||
return true; | ||
}) | ||
.map(function(field) { | ||
return [reptile.fieldName(field) , reptile.value(field,data[field])]; | ||
}) | ||
.slice(0,max || Infinity); | ||
d3.select(this) | ||
.append("table").classed("list",true) | ||
.selectAll("tr") | ||
.data(data) | ||
.enter() | ||
.append("tr") | ||
.selectAll("td") | ||
.data(Object) | ||
.enter() | ||
.append("td").text(Object); | ||
}; | ||
/* global reptile, d3 */ | ||
// ## Table | ||
// Renders an array as a table. Fields are optional | ||
reptile.table = function table(data,fields,max) { | ||
// If fields are not defined we loop through the whole dataset and fetch all the keys | ||
if (!fields) { | ||
fields = {}; | ||
data.forEach(function(d) { | ||
Object.keys(d).forEach(function(d) { | ||
fields[d] = true; | ||
}); | ||
}); | ||
fields = Object.keys(fields); | ||
} | ||
// Allow the order to be reversed | ||
if (this.dataset && this.dataset.reverse) data = data.reverse(); | ||
data = data.slice(0,max || Infinity); | ||
var tbl = d3.select(this).append("table").classed("reptable",true); | ||
tbl.append("thead") | ||
.append("tr") | ||
.selectAll("th") | ||
.data(fields.map(reptile.fieldName)) | ||
.enter() | ||
.append("th") | ||
.text(Object); | ||
tbl.append("tbody") | ||
.selectAll("tr") | ||
.data(data) | ||
.enter() | ||
.append("tr") | ||
.selectAll("td") | ||
.data(fields) | ||
.enter() | ||
.append("td") | ||
.html(function(field,i,j) { | ||
return reptile.value(field,data[j][field]); | ||
}); | ||
}; | ||
function wrap(fn) { | ||
return function(facts) { | ||
facts = facts[this.dataset.input]; | ||
var select = this.dataset.select, | ||
fields = this.dataset.fields && this.dataset.fields.split(","); | ||
if (select && select.length) select.split(".") | ||
.forEach(function(key) { | ||
facts = facts[key]; | ||
}); | ||
fn.call(this,facts,fields,this.dataset.max); | ||
}; | ||
} | ||
reptile.widgets.table = wrap(reptile.table); | ||
reptile.widgets.list = wrap(reptile.list); | ||
reptile.widgets.item = wrap(reptile.item); | ||
/* global reptile, d3, L */ | ||
var tiledef = { | ||
'SAT' : { link : 'https://{s}.tiles.mapbox.com/v3/zjonsson.hk6i67ff/{z}/{x}/{y}.png' }, | ||
'OSM' : { link : 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'}, | ||
'MQ' : { link: 'https://{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png'}, | ||
'MAPBOX' : { link : 'https://{s}.tiles.mapbox.com/v3/zjonsson.map-8g2l2dg2/{z}/{x}/{y}.png'} | ||
}; | ||
var maxZoom = 18; | ||
reptile.widgets.map = function(location) { | ||
var detail = +this.dataset.detail || 1; | ||
d3.select(this).classed("map",true).style("zoom",1/detail); | ||
// Define the map | ||
var map = this.map = this.map || L.map(this,{zoomControl:false,maxZoom:maxZoom}); | ||
if (this.bounds) map.fitBounds(this.bounds); | ||
else map.setView({lat:location.lat,lng:location.lng},this.dataset.zoom || 4); | ||
map.attributionControl.setPrefix(""); | ||
map._initPathRoot(); | ||
var g = d3.select(this).select("svg").append("g").attr("class","canvas"); | ||
var tiles = this.dataset.tiles; | ||
tiles = (tiledef[tiles] && tiledef[tiles].link) || tiles || 'https://{s}.tiles.mapbox.com/v3/zjonsson.map-6qn6dl9t/{z}/{x}/{y}.png'; | ||
// Set map tiles | ||
if (tiles == "vector") { | ||
new L.TileLayer.d3_geoJSON('http://{s}.tile.openstreetmap.us/vectiles-highroad/{z}/{x}/{y}.json',{class:"road"}) | ||
.addTo(map); | ||
new L.TileLayer.d3_geoJSON('http://tile.openstreetmap.us/vectiles-water-areas/{z}/{x}/{y}.json',{class:"water"}) | ||
.addTo(map); | ||
} else L.tileLayer(tiles).addTo(map); | ||
// Icon of the main house | ||
var size = 30 * detail; | ||
var main_house = g.append("image") | ||
.attr("height",size).attr("width",size) | ||
.attr("xlink:href","/img/house_icon.svg"); | ||
function refresh() { | ||
// Update location of main house | ||
var point = map.latLngToLayerPoint({lat:location.lat,lng:location.lng}); | ||
main_house.attr("transform","translate("+(point.x-size/2)+","+(point.y-size/2)+")"); | ||
} | ||
map.on("moveend",refresh); | ||
refresh(); | ||
return map; | ||
}; | ||
/* global reptile, d3 */ | ||
reptile.widgets.comps_table = function(comps) { | ||
var self = d3.select(this); | ||
self.append("span").classed("caption",true).text("Relevant Comps - Detail"); | ||
reptile.table.call(this,comps.RECORDS,['NO','DIST','DT','AMT','SA_SQFT','PSQFT'],+this.dataset.max); | ||
}; | ||
reptile.widgets.comps_orig_table = function(comps_orig) { | ||
var self = d3.select(this); | ||
self.append("span").classed("caption",true).text("Comps at origination - Detail"); | ||
reptile.table.call(this,comps_orig.RECORDS,['NO','DIST','DT','AMT','SA_SQFT','PSQFT'],+this.dataset.max); | ||
}; | ||
reptile.widgets.comps_payoff_table = function(comps_payoff) { | ||
if (comps_payoff.err) return d3.select(this).text(comps_payoff.err); | ||
var self = d3.select(this); | ||
self.append("span").classed("caption",true).text("Comps"); | ||
reptile.table.call(this,comps_payoff.RECORDS,['NO','DIST','DT','AMT','SA_SQFT','PSQFT']); | ||
}; | ||
reptile.widgets.comps_mod_table = function(comps_mod) { | ||
if (comps_mod.err) return d3.select(this).text(comps_mod.err); | ||
var self = d3.select(this); | ||
self.append("span").classed("caption",true).text("Comps"); | ||
reptile.table.call(this,comps_mod.RECORDS,['NO','DIST','DT','AMT','SA_SQFT','PSQFT']); | ||
}; | ||
function comps_summary(comps) { | ||
d3.select(this).append("div").classed("caption",true).text("Comps Summary"); | ||
var table = d3.select(this).append("table"); | ||
table.append("thead") | ||
.append("tr") | ||
.selectAll("th") | ||
.data(["","Transaction","Using Comps"]) | ||
.enter() | ||
.append("th") | ||
.text(Object); | ||
var data = [ ["Price / Sqft",reptile.format.round(comps.PSQFT.STATED),reptile.format.round(comps.PSQFT.COMPS)] ]; | ||
if (comps.LTV) data.push(["LTV",reptile.format.perc(comps.LTV.STATED),reptile.format.perc(comps.LTV.COMPS)]); | ||
data.push(["Value",reptile.format.dround(comps.VALUE.STATED),reptile.format.dround(comps.VALUE.COMPS)]); | ||
data.push(["Difference",reptile.format.dround(comps.VALUE.STATED - comps.VALUE.COMPS),""]); | ||
table.append("tbody") | ||
.selectAll("tr") | ||
.data(data) | ||
.enter() | ||
.append("tr") | ||
.selectAll("td") | ||
.data(Object) | ||
.enter() | ||
.append("td") | ||
.text(Object); | ||
} | ||
reptile.widgets.comps_ltv_summary = function(comps,sqft,mtg_last) { | ||
d3.select(this).append("div").classed("caption",true).text("Comps Summary"); | ||
var table = d3.select(this).append("table"); | ||
table.append("tbody") | ||
.selectAll("tr") | ||
.data([ | ||
["Price / Sqft",reptile.format.round(comps.median)], | ||
["Value",reptile.format.dround(sqft*comps.median)], | ||
["Balance",reptile.format.dround(mtg_last.BALANCE)], | ||
["LTV",reptile.format.perc(mtg_last.BALANCE / (sqft*comps.median))] | ||
]) | ||
.enter() | ||
.append("tr") | ||
.selectAll("td") | ||
.data(Object) | ||
.enter() | ||
.append("td") | ||
.text(Object); | ||
}; | ||
reptile.widgets.comps_orig_summary = function(comps_orig,sqft,mtg_timeline,mtg_orig) { | ||
comps_summary.call(this,comps_orig,sqft,mtg_timeline,mtg_orig); | ||
}; | ||
reptile.widgets.comps_summary = function(comps,sqft) { | ||
comps.PSQFT = {COMPS : comps.MEDIAN}; | ||
comps.LTV = {}; | ||
comps.VALUE = {COMPS: comps.ESTIMATE}; | ||
comps_summary.call(this,comps,sqft); | ||
}; | ||
reptile.widgets.comps_payoff_summary = function(comps_payoff,sqft,mtg_timeline,mtg_orig) { | ||
comps_summary.call(this,comps_payoff,sqft,mtg_timeline,mtg_orig); | ||
}; | ||
reptile.widgets.comps_mod_summary = function(comps_mod,sqft,mtg_timeline,mtg_orig) { | ||
comps_summary.call(this,comps_mod,sqft,mtg_timeline,mtg_orig); | ||
}; | ||
function comps_map(comps,location) { | ||
var b,o; | ||
var charge = +d3.select(this).attr('data-charge') || 130; | ||
var detail = +this.dataset.detail || 1; | ||
if (comps.err) return d3.select(this).text(comps.err); | ||
var select = this.dataset.select || "RECORDS"; | ||
var records = comps; | ||
if (select) select.split(".") | ||
.forEach(function(key) { | ||
records = records[key]; | ||
}); | ||
records = records.slice(0,this.dataset.max || Infinity); | ||
// Calculate boundaries | ||
var latB = d3.extent(records,function(d) { return d.LAT;}), | ||
lngB = d3.extent(records,function(d) { return d.LNG;}), | ||
bounds = [[latB[0],lngB[0]],[latB[1],lngB[1]]]; | ||
this.bounds = bounds; | ||
var map = reptile.widgets.map.call(this,location); | ||
var g = d3.select(this).select(".canvas"); | ||
// Define the labelForce | ||
var radius = 10; | ||
var labelForce = d3.force_labels() | ||
.linkDistance(0) | ||
.gravity(0) | ||
.charge(-charge*Math.pow(detail,2)) | ||
.friction(0.8) | ||
.on("tick",function() { | ||
anchors.each(function(d) { | ||
d = d.labelPos; | ||
d.x = Math.min(Math.max(b.min.x+radius,d.x),b.max.x-radius); | ||
d.y = Math.min(Math.max(b.min.y+radius,d.y),b.max.y-radius); | ||
}); | ||
}); | ||
// Place anchors of the comps | ||
var anchors = g.selectAll(".comp_house") | ||
.data(records).enter() | ||
.append("circle") | ||
.attr("class",function(d) { return "comp_house "+(d.PSQFT > comps.BENCHMARK ? "over" : (d.PSQFT <= comps.BENCHMARK ) ? "under" : "none"); }) | ||
.attr("r",4) | ||
.style("stroke","black") | ||
.style("stroke-width","0.5px"); | ||
// Define links and labels | ||
var links = g.selectAll(".link") | ||
.data(records).enter() | ||
.append("line") | ||
.attr("class","link") | ||
.style("stroke-width","0.5px") | ||
.style("stroke","black"); | ||
var labels = g.selectAll(".labels") | ||
.data(records).enter() | ||
.append("g") | ||
.classed("labels",true) | ||
.attr("class",function(d) { return "labels "+(d.PSQFT > comps.BENCHMARK ? "over" : (d.PSQFT <= comps.BENCHMARK) ? "under" : "none"); }); | ||
labels.append("circle") | ||
.attr("r",radius) | ||
.style("stroke","black"); | ||
labels.append("text") | ||
.text(function(d,i) { return i+1;}) | ||
.style("text-anchor","middle") | ||
.style("font-size","80%") | ||
.attr("dy","0.4em"); | ||
function refresh() { | ||
// Update pixel points of all the comps | ||
records.forEach(function(d) { | ||
d.point = map.latLngToLayerPoint({lat:d.LAT,lng:d.LNG}); | ||
}); | ||
// Calculate the pixel bounding box and min/max | ||
b = map.getPixelBounds(); | ||
o = map.getPixelOrigin(); | ||
var p= 30; | ||
b.max.y -= o.y; | ||
b.min.y -= o.y; | ||
b.min.x -= o.x+p; | ||
b.max.x -= o.x+p; | ||
// Relocate the anchors | ||
anchors.attr("cx",function(d) { return d.point.x;}) | ||
.attr("cy",function(d) { return d.point.y;}) | ||
.call(labelForce.update); | ||
// Tick the labelForce | ||
labelForce.start(); | ||
// while (labelForce.alpha()>0.05 && i++ < 100) labelForce.tick(); | ||
function relocate() { | ||
// Relocate labels and links | ||
labels.attr("transform",function(d) { return "translate("+d.labelPos.x+" "+d.labelPos.y+") scale("+detail+")"; }); | ||
links.attr("x1",function(d) { return d.labelPos.x;}) | ||
.attr("y1",function(d) { return d.labelPos.y;}) | ||
.attr("x2",function(d) { return d.anchorPos.x;}) | ||
.attr("y2",function(d) { return d.anchorPos.y;}); | ||
} | ||
labelForce.on('tick',relocate); | ||
} | ||
map.on("moveend",refresh); | ||
refresh(); | ||
} | ||
reptile.widgets.comps_map = function(comps,location) { | ||
this.dataset.input = comps; | ||
comps.BENCHMARK = comps.BENCHMARK || comps.MEDIAN; | ||
comps_map.call(this,comps,location); | ||
}; | ||
reptile.widgets.comps_payoff_map = function(comps_payoff,location) { | ||
this.dataset.input = comps_payoff; | ||
comps_map.call(this,comps_payoff,location); | ||
}; | ||
reptile.widgets.comps_orig_map = function(comps_orig,location) { | ||
d3.select(this).classed('compsorig',true); | ||
this.dataset.input = comps_orig; | ||
comps_map.call(this,comps_orig,location); | ||
}; | ||
reptile.widgets.comps_mod_map = function(comps_mod,location) { | ||
this.dataset.input = comps_mod; | ||
comps_map.call(this,comps_mod,location); | ||
}; | ||
/* global reptile, d3 */ | ||
reptile.widgets.est_rent = function(est_rent) { | ||
var svg = d3.select(this).append("div").append("svg").attr("height","350").attr("width","350"); | ||
var data = est_rent.map(function(d) { | ||
return {x:new Date(d.DT),y:d.EST_RENT}; | ||
}); | ||
var chart = reptile.baseChart(svg) | ||
.setTitle("chart","Estimated Monthly Rent") | ||
.scale("x",d3.time.scale()); | ||
chart.enter([data]).append("path").attr("class","line"); | ||
chart.render(); | ||
}; | ||
/* global d3, reptile */ | ||
var UNIQUE_ID = 0; | ||
var weight = 0.4; | ||
reptile.widgets.capstructure = function(mtg_orig,mtg_timeline,property_history) { | ||
UNIQUE_ID++; | ||
mtg_timeline = [{DIST_DATE:mtg_orig.ORIG_DATE,BALANCE:mtg_orig.ORIG_AMT}].concat(mtg_timeline.slice()); | ||
property_history = property_history.filter(function(d) { | ||
return d.YM > 290; | ||
}); | ||
var lastDate; | ||
var max=0,lastPrice,step = 1.2; | ||
var history = property_history.map(function(d) { | ||
if (!lastPrice) lastPrice = d.PRICE; | ||
lastPrice = weight * Math.max(lastPrice/step, Math.min(d.PRICE,lastPrice*step)) + (1-weight) * lastPrice; | ||
max = Math.max(max,lastPrice); | ||
lastDate = new Date(1980,d.YM,1); | ||
return { | ||
x : new Date(1980,d.YM,1), | ||
y0 : 0, | ||
y1 : lastPrice | ||
}; | ||
}); | ||
if (mtg_timeline.length == 1) mtg_timeline.push({DIST_DATE:lastDate,BALANCE:mtg_orig.ORIG_AMT}); | ||
history = history.filter(function(d) { | ||
return d.x.valueOf() > new Date(mtg_orig.ORIG_DATE) && d.x < new Date(mtg_timeline[mtg_timeline.length-1].DIST_DATE); | ||
}); | ||
var prepaid; | ||
mtg_timeline = mtg_timeline.filter(function(d) { | ||
if (prepaid) return false; | ||
if (d.MBA_STAT == '0') prepaid = true; | ||
return new Date(d.DIST_DATE) <= lastDate; | ||
}); | ||
var capital = mtg_timeline.map(function(d) { | ||
var bal = d.INV_BALANCE || d.BALANCE, | ||
first = (mtg_orig.LIEN == 2) ? mtg_orig.OTHER_AMT : bal, | ||
second = (mtg_orig.LIEN == 2) ? bal : mtg_orig.OTHER_AMT; | ||
max = Math.max(max,first+second); | ||
return { | ||
x : new Date(d.DIST_DATE), | ||
y0 : 0, | ||
y1 : first+second, | ||
first : first, | ||
second : second | ||
}; | ||
}); | ||
var firstLien = capital.map(function(d) { | ||
return {x:d.x,y0:0,y1:d.first}; | ||
}), | ||
secondLien = capital.map(function(d) { | ||
return {x:d.x,y0:d.first,y1:d.y1}; | ||
}), | ||
clipCapitalup = capital.map(function(d) { | ||
return {x:d.x, y0:d.y1,y1:max}; | ||
}), | ||
clipHistoryUp = history.map(function(d) { | ||
return {x:d.x,y0:d.y1,y1:max}; | ||
}), | ||
clipHistoryDown = history.map(function(d) { | ||
return {x:d.x,y0:0,y1:d.y1}; | ||
}); | ||
var svg = d3.select(this); | ||
var chart = reptile.baseChart(svg) | ||
.setTitle("chart","Est. Capital Structure") | ||
.scale("x",d3.time.scale()) | ||
.add([clipCapitalup],function(g) { | ||
g.append("clipPath") | ||
.attr("id","capitalup"+UNIQUE_ID) | ||
.append("path"); | ||
}) | ||
.add([clipHistoryUp],function(g) { | ||
g.append("clipPath") | ||
.attr("id","historyup"+UNIQUE_ID) | ||
.append("path"); | ||
}) | ||
.add([clipHistoryDown],function(g) { | ||
g.append("clipPath") | ||
.attr("id","historydown"+UNIQUE_ID) | ||
.append("path"); | ||
}) | ||
.add([history],function(g) { | ||
g.append("path") | ||
.attr("class","line") | ||
.style("fill","green") | ||
.attr("data-legend","Positive Equity") | ||
.attr("clip-path","url("+document.location+"#capitalup"+UNIQUE_ID+")"); | ||
}) | ||
.add([capital],function(g) { | ||
g.append("path") | ||
.attr("class","line") | ||
.attr("data-legend","Negative Equity") | ||
.attr("clip-path","url("+document.location+"#historyup"+UNIQUE_ID+")") | ||
.style("fill","red"); | ||
}) | ||
.add([firstLien],function(g) { | ||
g.append("path") | ||
.style("fill","blue") | ||
.attr("data-legend","First Lien") | ||
.attr("clip-path","url("+document.location+"#historydown"+UNIQUE_ID+")"); | ||
}) | ||
.add([firstLien],function(g) { | ||
g.append("path") | ||
.attr("class","line"); | ||
}); | ||
if (capital[0].second > 100 ) { | ||
chart = chart | ||
.add([secondLien],function(g) { | ||
g.append("path") | ||
.style("fill","LightSkyBlue") | ||
.attr("data-legend","Second Lien") | ||
.attr("clip-path","url("+document.location+"#historydown"+UNIQUE_ID+")"); | ||
}) | ||
.add([secondLien],function(g) { | ||
g.append("path") | ||
.attr("class","line"); | ||
}) | ||
.add([history],function(g) { | ||
g.append("path") | ||
.attr("class","line"); | ||
}) | ||
.add((mtg_orig.LIEN == 2) ? [firstLien] : [secondLien],function(g) { | ||
g.append('path') | ||
.style('fill','white') | ||
.attr('opacity',0.3); | ||
}); | ||
} | ||
chart.axis('x',function(d) { d.ticks(6); }) | ||
.tickFormat('y',d3.format('.2s')) | ||
.render(); | ||
}; | ||
/* global reptile, d3 */ | ||
reptile.widgets.transaction_orig = function(_transaction_orig) { | ||
var self = d3.select(this); | ||
self.append("div").classed("caption",true).text('Matched Transaction'); | ||
var div = self.append("div"); | ||
reptile.list.call(div.node(),_transaction_orig,['SR_BUYER','SR_SELLER','SR_VAL_TRANSFER','SR_DATE_TRANSFER','SR_DATE_FILING','SR_LNDR_FIRST_NAME_1','SR_LOAN_VAL_1']); | ||
}; | ||
reptile.widgets.transaction_payoff = function(transaction_payoff) { | ||
d3.select(this).append("div").classed("caption",true).text('Matched Liquidation'); | ||
var div = d3.select(this).append("div"); | ||
reptile.list.call(div.node(),transaction_payoff,['SR_BUYER','SR_SELLER','SR_VAL_TRANSFER','SR_DATE_TRANSFER','SR_DATE_FILING','SR_LNDR_FIRST_NAME_1','SR_LOAN_VAL_1','CITY','STATE','ZIP']); | ||
}; | ||
reptile.widgets.transaction_mod = function(comps_mod) { | ||
d3.select(this).append("div").classed("caption",true).text('Modification Detail'); | ||
var div = d3.select(this).append("div"); | ||
reptile.list.call(div.node(),comps_mod.MOD,['MOD_DATE','PRE_AMT','POST_BAL','PRE_INT','POST_INT']); | ||
}; | ||
reptile.widgets.assessor = function(_assessor,_assessor_address,mtg_orig) { | ||
var self = d3.select(this); | ||
var data = {}; | ||
_assessor = _assessor || {}; | ||
_assessor_address = _assessor_address || {}; | ||
//_assessor_address = _assessor_address || {}; | ||
Object.keys(_assessor).forEach(function(key) { data[key] = _assessor[key];}); | ||
Object.keys(mtg_orig).forEach(function(key) { data[key] = mtg_orig[key];}); | ||
Object.keys(_assessor_address).forEach(function(key) { data[key] = _assessor_address[key];}); | ||
if (data.FULL_ADDRESS) data.ADDR = data.FULL_ADDRESS; | ||
else data.ADDR = 'Undetermined'; | ||
data.HOUSE_SQFT = data.SA_SQFT; | ||
data.HOUSE_ACRES = data.SA_LOTSIZE / 43560; | ||
self.append("div").classed("caption",true).text('Matched Property'); | ||
var div = self.append('div'); | ||
reptile.list.call(div.node(),data,['ADDR','CITY','STATE','ZIP','USE_CODE_STD_TXT','HOUSE_SQFT','HOUSE_ACRES','SA_YR_BLT','SA_NBR_BEDRMS','SA_NBR_BATH','SA_BLDG_CODE_TXT','SA_EXTERIOR_1_CODE_TXT','SA_FOUNDATION_CODE_TXT']); | ||
}; | ||
/* global reptile, d3, dpl */ | ||
reptile.widgets.joint = function(mbs_timeline,property_history,equifax) { | ||
var svg = d3.select(this).append("svg"); | ||
var svg1 = svg.append("svg") | ||
.datum({x0:0,y1:0,y0:0.30,width:1}) | ||
.attr({id:'svg1','data-scale-x':'cw','data-scale-y':'ch','data-static':true}) | ||
.call(dpl.render); | ||
var s1 = reptile.widgets.capstructure.call(svg1.node(),mbs_timeline,property_history) | ||
.tickFormat('x',function() {}) | ||
.axis('x',function(d) { d.ticks(10);}) | ||
.margin({left:50,right:0,top:3,bottom:3}) | ||
.render(); | ||
var svg2 =svg.append("svg") | ||
.datum({x0:0,y1:0.3,y0:0.60,width:1}) | ||
.attr({id:'svg2','data-scale-x':'cw','data-scale-y':'ch','data-static':true}) | ||
.call(dpl.render); | ||
var s2 = reptile.widgets.dlq.call(svg2.node(),mbs_timeline) | ||
.tickFormat('x',function() {}) | ||
.axis('x',function(d) { d.ticks(10);}) | ||
.margin({left:50,right:0,top:3,bottom:3}) | ||
.render(); | ||
var svg3 = svg.append('svg') | ||
.datum({x0:0,y1:0.6,y0:0.9,width:1}) | ||
.attr({id:'svg2','data-scale-x':'cw','data-scale-y':'ch','data-static':true}) | ||
.call(dpl.render); | ||
var s3 = reptile.widgets.vantage.call(svg3.node(),equifax) | ||
.margin({left:50,right:0,top:3,bottom:10}) | ||
.axis('x',function(d) { d.ticks(10);}) | ||
.render(); | ||
var e1 = s1.scale('x').domain(), | ||
e2 = s2.scale('x').domain(); | ||
var extent = [Math.min(e1[0],e2[0]),Math.max(e1[1],e2[1])]; | ||
s1.scale('x').domain(extent); | ||
s1.on('resize.autofit',function() {}).render(); | ||
s2.scale('x').domain(extent); | ||
s2.on('resize.autofit',function() {}).render(); | ||
s3.scale('x').domain(extent); | ||
s3.on('resize.autofit',function() {}).render(); | ||
svg.selectAll("svg").attr("viewbox","").call(dpl.render); | ||
}; | ||
/* global reptile,d3 */ | ||
reptile.widgets.property_history = function(property_history) { | ||
if (!property_history.length) return; | ||
property_history = property_history.filter(function(d) { | ||
return d.YM > 288; | ||
}); | ||
var lastDate, | ||
maxPrice=0, | ||
step = 1.2, | ||
weight = 0.4, | ||
lastPrice; | ||
var history = property_history.map(function(d) { | ||
if (!lastPrice) lastPrice = d.PRICE; | ||
lastPrice = weight * Math.max(lastPrice/step, Math.min(d.PRICE,lastPrice*step)) + (1-weight) * lastPrice; | ||
maxPrice = Math.max(maxPrice,lastPrice); | ||
lastDate = new Date(1980,d.YM,1); | ||
return { | ||
x : new Date(1980,d.YM,1), | ||
y0 : 0, | ||
y1 : lastPrice | ||
}; | ||
}) | ||
.filter(function(d) { | ||
return d.x > new Date(2006,6,1); | ||
}); | ||
var svg = d3.select(this); | ||
return reptile.baseChart(svg) | ||
.setTitle("chart","AVM Estimate") | ||
.scale("x",d3.time.scale()) | ||
.add([history],function(g) { | ||
g.append("path") | ||
.attr("class","line") | ||
.style("fill","green") | ||
.style('opacity','0.85'); | ||
}) | ||
.axis('x',function(d) { d.ticks(6); }) | ||
.tickFormat('y',d3.format('.2s')) | ||
.render(); | ||
}; | ||
reptile.widgets.assessor_history = function(assessor_history) { | ||
var self = d3.select(this); | ||
self.append("span").classed("caption",true).text("Assessor History"); | ||
reptile.table.call(this,assessor_history,['YEAR','SA_VAL_ASSD','SA_TAX_VAL']); | ||
}; | ||
/* global reptile, d3 */ | ||
reptile.widgets.building_permits = function(building_permits) { | ||
if (!building_permits.length) return; | ||
building_permits = building_permits.slice(24).map(function(d) { | ||
return { | ||
x : new Date(d.YEAR,d.MONTH-1,1), | ||
y1: d.COST_12M || 0, | ||
y0:0 | ||
}; | ||
}) | ||
.filter(function(d) { | ||
return d.x > new Date(2006,6,1); | ||
}); | ||
var svg = d3.select(this); | ||
return reptile.baseChart(svg) | ||
.setTitle("chart","Building Permits") | ||
.scale("x",d3.time.scale()) | ||
.add([building_permits],function(g) { | ||
g.append("path") | ||
.attr("class","line") | ||
.style("fill","green") | ||
.style('opacity','0.85'); | ||
}) | ||
.axis('x',function(d) { d.ticks(6); }) | ||
.tickFormat('y',d3.format('.2s')) | ||
.render(); | ||
}; | ||
/* global reptile, d3 */ | ||
reptile.widgets.orig = function(mtg_orig) { | ||
var self = d3.select(this); | ||
self.append("div").classed("caption",true).text("Loan Origination"); | ||
var loan = mtg_orig; | ||
var div = self.append("div"); | ||
reptile.list.call(div.node(),loan,['ORIG_AMT','LIEN_POSITION','APPRAISAL','ORIG_DATE','MATURITY_DATE','ORIG_INTEREST','FICO','OCCUPANCY','PURPOSE','FIRST_AMT','SECOND_AMT','ORIG_LTV','ORIG_COMB_LTV']); | ||
}; | ||
/* global reptile, d3 */ | ||
var Problems = { | ||
APPRAISAL : 'Appraisal issue', | ||
LIQUIDATION : 'Liquidation Issue', | ||
MOD : 'Modification Issue', | ||
OWNER_OCC : 'Owner Occupancy Issue', | ||
FAILED_FC : 'Failed Foreclosure', | ||
FAILED_REO : 'Failed REO', | ||
LONG_DLQ : 'Unresolved Delinquency', | ||
EPD : 'Early Payments Default' | ||
}; | ||
reptile.widgets.problems = function(problems) { | ||
var self = d3.select(this); | ||
self.append("div").classed("caption",true).text("Potential Issues"); | ||
var tr = self.append("table") | ||
.selectAll("tr") | ||
.data(Object.keys(Problems).filter(function(key) { | ||
return true; //problems[key] !== undefined; | ||
})) | ||
.enter() | ||
.append("tr"); | ||
tr.append("td") | ||
.append("input") | ||
.attr("type","checkbox") | ||
.property("checked",function(d) { | ||
return (problems[d] === 1) && !problems[d].err; | ||
}) | ||
.property("disabled",function(d) { | ||
return (problems[d] === null) && "disabled"; | ||
}); | ||
tr.append("td").text(function(d) { return Problems[d];}); | ||
}; | ||
/* global reptile, d3 */ | ||
reptile.widgets.mtg_timeline = function(mtg_timeline) { | ||
reptile.table.call(this,mtg_timeline,['DIST_DATE','INV_BALANCE','SCH_PNI','RATE','MBA_STATUS','EST_ADVANCE','CURR_LOSS','CUMUL_LOSS'],+this.dataset.max); | ||
}; | ||
reptile.widgets.timeline = function(timeline) { | ||
d3.select(this).classed('timeline',true); | ||
var self = d3.select(this); | ||
self.append("div").classed("caption",true).text("Timeline of Events"); | ||
reptile.table.call(self.append('div').node(),timeline,['NO','DT','txt']); | ||
}; | ||
/* global reptile, d3 */ | ||
var loss_titles = { | ||
INV_BALANCE : 'Inv. Balance', | ||
PROCEEDS : ' - Proceeds', | ||
EST_ADVANCE : ' + Est. Advance', | ||
LIQ_COST : ' + Other Cost', | ||
LIQ_LOSS : '= Liq. Loss', | ||
PRIOR_LOSS : ' + Mod. Loss', | ||
TOTAL_LOSS : '= Total Loss' | ||
}; | ||
reptile.widgets.loss_analysis = function(_loss_analysis,mtg_timeline) { | ||
var self = d3.select(this), | ||
last = mtg_timeline[mtg_timeline.length-1], | ||
data; | ||
var balance = (_loss_analysis && _loss_analysis.INV_BALANCE || last.INV_BALANCE); | ||
last.CUMUL_LIQ_LOSS = last.CUMUL_LIQ_LOSS || last.CUMUL_LOSS; | ||
last.CUMUL_MOD_LOSS = last.CUMUL_MOD_LOSS || 0; | ||
if (!_loss_analysis) | ||
data = [ | ||
{BLANK:'Est. Advance',AMOUNT: last.EST_ADVANCE}, | ||
{BLANK:'Liq Loss',AMOUNT:last.CUMUL_LIQ_LOSS}, | ||
{BLANK:'Net Proceeds',AMOUNT:last.CUMUL_LIQ_LOSS-last.EST_ADVANCE}, | ||
{BLANK:'Mod Loss',AMOUNT:last.CUMUL_MOD_LOSS}, | ||
{BLANK:'Total Loss',AMOUNT:last.CUMUL_LOSS} | ||
]; | ||
else data = Object.keys(_loss_analysis) | ||
.filter(function(d) { | ||
return d !== 'LIQ_DATE'; | ||
}) | ||
.map(function(txt) { | ||
return {BLANK:'<span style="white-space:pre-wrap">'+loss_titles[txt]+'</span>',AMOUNT:_loss_analysis[txt],PERC_BAL:_loss_analysis[txt]/_loss_analysis.INV_BALANCE}; | ||
}); | ||
data.forEach(function(d) { | ||
d.PERC_BAL = d.AMOUNT / balance; | ||
}); | ||
self.append('div').classed('caption',true).text('Loss Analysis'); | ||
reptile.table.call(this,data,['BLANK','AMOUNT','PERC_BAL']); | ||
}; | ||
reptile.widgets.active_status = function(mtg_timeline) { | ||
var self = d3.select(this); | ||
var last = mtg_timeline[mtg_timeline.length-1]; | ||
mtg_timeline.slice().reverse().forEach(function(d) { | ||
if (!last.LAST_MOD_RATE && d.POST_INT) last.LAST_MOD_RATE = d.POST_INT; | ||
}); | ||
self.append("div").classed("caption",true).text("Last Status"); | ||
reptile.list.call(this,last,['BALANCE','INV_BALANCE','SCH_PI','MBA_STATUS','EST_ADVANCE','RATE','LAST_MOD_RATE','CUMUL_LOSS']); | ||
}; | ||
reptile.widgets.last_status = function(loan_no,mtg_timeline,_loss_analysis) { | ||
var last = mtg_timeline[mtg_timeline.length-1]; | ||
if (!last) return reptile.widgets.active_status.call(this,[{MBA_STATUS:'Not reported'}]); | ||
if ((!last.MBA_STATUS || last.MBA_STATUS == '0') && last.CUMUL_LOSS && last.CUMUL_SEV) | ||
reptile.widgets.loss_analysis.call(this,_loss_analysis,mtg_timeline); | ||
else | ||
reptile.widgets.active_status.call(this,mtg_timeline); | ||
}; | ||
/* global reptile, d3 */ | ||
var statusCaption = { | ||
'D' : 'Delinquent', | ||
'C' : 'Current', | ||
'F' : 'Foreclosure', | ||
'M' : 'Modification', | ||
'0' : 'Liquidated', | ||
'R' : 'REO', | ||
'B' : 'Bankrupcy', | ||
'U' : 'Not Reported' | ||
}; | ||
reptile.widgets.dlq = function(mtg_timeline,mtg_orig) { | ||
var dlq = [], | ||
curr = {}, | ||
last; | ||
mtg_timeline = [{DIST_DATE : mtg_orig.ORIG_DATE, STATUS : 'U',BALANCE : mtg_orig.ORIG_AMT}].concat(mtg_timeline.slice()); | ||
if (mtg_timeline.length == 1) mtg_timeline.push({DIST_DATE:new Date(),STATUS:'U',BALANCE:mtg_orig.ORIG_AMT}); | ||
mtg_timeline.forEach(function(d) { | ||
var status = d.STATUS; | ||
if (!status) status = 'O'; | ||
if (!isNaN(status) && status != '0') status = 'D'; | ||
curr.x1 = new Date(d.DIST_DATE); | ||
if (curr.STATUS != status) { | ||
curr = { | ||
STATUS:status, | ||
x0:new Date(d.DIST_DATE), | ||
x1:new Date(d.DIST_DATE), | ||
y1 : 0, | ||
y0 : 1 | ||
}; | ||
dlq.push(curr); | ||
last = d; | ||
} | ||
}); | ||
last = dlq[dlq.length-1]; | ||
if (last.x0.valueOf() == last.x1.valueOf()) last.x1 = new Date(last.x0.getFullYear(),last.x0.getMonth()+1,last.x0.getDate()); | ||
var estAdv = mtg_timeline.map(function(d) { | ||
return { | ||
x: new Date(d.DIST_DATE), | ||
y: d.EST_ADVANCE || 0 | ||
}; | ||
}); | ||
var svg = d3.select(this).classed('dlq',true); | ||
reptile.baseChart(svg) | ||
.showAxes(['x','y','y2']) | ||
.setTitle("chart","Est. Servicer Advance") | ||
.axis('x',function(d) { d.ticks(7); }) | ||
.tickFormat('x',reptile.timeFormat) | ||
.tickFormat('y',d3.format('.2s')) | ||
.tickFormat('y2',d3.format('.1%')) | ||
.setTitle('y_top','$Adv') | ||
.setTitle('y2_top','%Loan') | ||
.add(dlq,function(g) { | ||
g.append("rect") | ||
.attr("class",function(d) { return "rect S"+d.STATUS;}) | ||
.attr("data-scale-y","ch") | ||
.attr("data-legend",function(d) { return statusCaption[d.STATUS] || d.STATUS;}); | ||
}) | ||
.add([estAdv],function(g) { | ||
g.append("path").attr("class","whiteline"); | ||
}) | ||
.add([estAdv],function(g) { | ||
g.append("path").attr("class","line"); | ||
}) | ||
.on('resize.y2',function() { | ||
var extent = this.scale('y').domain(); | ||
this.scale('y2').domain([extent[0]/last.BALANCE,extent[1]/last.BALANCE]); | ||
}) | ||
.render(); | ||
}; | ||
/* global dpl, reptile, d3, Promise */ | ||
reptile.widgets.info1_icons = function(info1_icons) { | ||
if (!info1_icons.length) return; | ||
var g = d3.select(this); | ||
g.append("div") | ||
.classed("caption",true) | ||
.text("Household Characteristics"); | ||
g.selectAll('img') | ||
.data(info1_icons) | ||
.enter() | ||
.append('img') | ||
.attr('src',function(d) {return '/infoicon/'+d+'.svg';}) | ||
.style({height:"5%",width:"5%"}); | ||
}; | ||
reptile.widgets.vantage = function(equifax) { | ||
var svg = d3.select(this); | ||
var data = equifax.map(function(d) { | ||
return {x:new Date(d.date),y:d.vantage}; | ||
}); | ||
var chart = reptile.baseChart(svg) | ||
.showAxes(['x','y']) | ||
.setTitle("chart","Vantage Score") | ||
.scale("x",d3.time.scale()) | ||
.tickFormat('y',d3.format('.2s')); | ||
chart.enter([data]) | ||
.append("path") | ||
.attr("class","line") | ||
.render(); | ||
return chart; | ||
}; | ||
reptile.widgets.vantage2 = function(transunion) { | ||
if (!transunion.length) throw "# No Data"; | ||
var svg = d3.select(this); | ||
var data = transunion.map(function(d) { | ||
return {x:new Date(d.FILE_DATE),y:d.VTG2ATOT}; | ||
}); | ||
var chart = reptile.baseChart(svg) | ||
.showAxes(['x','y']) | ||
.setTitle("chart","Vantage Score") | ||
.scale("x",d3.time.scale()) | ||
.tickFormat('y',d3.format('.2s')); | ||
chart.enter([data]) | ||
.append("path") | ||
.attr("class","line"); | ||
chart.render(); | ||
return chart; | ||
}; | ||
reptile.widgets.tu_income = function(transunion) { | ||
var svg = d3.select(this); | ||
var data = transunion.map(function(d) { | ||
return {x:new Date(d.FILE_DATE),y:d.TIE03TOT}; | ||
}); | ||
var chart = reptile.baseChart(svg) | ||
.showAxes(['x','y']) | ||
.setTitle("chart","Estimated Income") | ||
.scale("x",d3.time.scale()) | ||
.tickFormat('y',d3.format('.2s')); | ||
chart.enter([data]) | ||
.append("path") | ||
.attr("class","line"); | ||
chart.render(); | ||
return chart; | ||
}; | ||
var baltypes = [ | ||
['MT','Mortgage'], | ||
['RE','Revolving'], | ||
['BC','Credit Cards'], | ||
['HI','Home Equity Loan'], | ||
['HR','HELOC'], | ||
['IN','Installment'] | ||
]; | ||
reptile.widgets.balance = function(transunion) { | ||
var res = {}; | ||
baltypes.forEach(function(d) { | ||
res[d] = []; | ||
}); | ||
transunion.forEach(function(d,i) { | ||
baltypes.forEach(function(tp) { | ||
var code = tp[0], | ||
cu = ((d[code+'33'] > -1 && d[code+'33']) || (res[tp][i-1] && res[tp][i-1].val) || 0); | ||
}); | ||
}); | ||
}; | ||
function credit(transunion,code,title) { | ||
var svg = d3.select(this); | ||
var curr=[],dlq=[]; | ||
transunion.forEach(function(d,i) { | ||
var dt = new Date(d.FILE_DATE), | ||
dl = (d[code+'57'] > -1 && d[code+'57']) || (dlq[i-1] && dlq[i-1].val) || 0, | ||
cu = ((d[code+'33'] > -1 && d[code+'33']) || (curr[i-1] && curr[i-1].val) || 0); | ||
dlq.push( { x: dt, y0:0, val:dl, y1 : dl}); | ||
curr.push( {x: dt, y0:dl, val:cu, y1: cu}); | ||
}); | ||
var max = 0; | ||
curr.forEach(function(d) { | ||
max = Math.max(max,d.y1); | ||
}); | ||
if (!max) throw "#no data"; | ||
var chart = reptile.baseChart(svg) | ||
.setTitle('chart',title) | ||
.showAxes(['x','y']) | ||
.scale("x",d3.time.scale()) | ||
.tickFormat('y',d3.format('.2s')); | ||
chart.enter([dlq]) | ||
.append("path") | ||
.attr("class","area") | ||
.style('fill','red') | ||
.attr("data-legend",'Delinquent'); | ||
chart.enter([curr]) | ||
.append("path") | ||
.attr("class","area") | ||
.style('fill','green') | ||
.attr("data-legend",'Current'); | ||
chart.render(); | ||
return chart; | ||
} | ||
reptile.widgets.tu_revolving = function(transunion) { | ||
return credit.call(this,transunion,'RE','Revolving'); | ||
}; | ||
reptile.widgets.tu_cards = function(transunion) {// | ||
return credit.call(this,transunion,'BC','Credit Cards'); | ||
}; | ||
reptile.widgets.tu_hi = function(transunion) {// | ||
return credit.call(this,transunion,'HI','Home Equity Loan'); | ||
}; | ||
reptile.widgets.tu_heloc = function(transunion) {// | ||
return credit.call(this,transunion,'HR','HELOC'); | ||
}; | ||
reptile.widgets.tu_in = function(transunion) { | ||
return credit.call(this,transunion,'IN','Installment'); | ||
}; | ||
reptile.widgets.tu_mt = function(transunion) {// | ||
return credit.call(this,transunion,'MT','Mortgage'); | ||
}; | ||
var categories = { | ||
'RE' : 'Revolving', | ||
'CB' : 'Credit Cards', | ||
'HI' : 'Home Equity Loan', | ||
'HR' : 'HELOC', | ||
'IN' : 'Installment', | ||
'MT' : 'Mortgage' | ||
}; | ||
reptile.widgets.credit = function(transunion,timeline) { | ||
var self = this; | ||
var keys = ['dlq','capstructure','vantage2','tu_mt','tu_revolving','tu_hi','tu_heloc','tu_in','tu_cards']; | ||
var svg = d3.select(this).append('svg').style({height:'100%',width:'100%'}); | ||
var height = svg.property("offsetHeight"); | ||
var width = svg.property("offsetWidth"); | ||
var charts = keys.map(function(key,i) { | ||
var chart = svg.append('svg').attr({x:0,y:height/keys.length*i,height:height*1/keys.length,width:width}).attr('data-reptile',key); | ||
return self._reptile_api.render(chart.node()); | ||
}); | ||
var x = [Infinity,-Infinity],totalWeight = 0; | ||
return Promise.settle(charts) | ||
.then(function(obj) { | ||
charts = obj.filter(function(d) { | ||
return !d.isRejected(); | ||
}) | ||
.map(function(d,i) { | ||
d = d.value(); | ||
totalWeight += (d.weight = (i<2) ? 1.5 : 1); | ||
var extent = dpl.chart(d).scale('x').domain(); | ||
x[0] = Math.min(extent[0],x[0].valueOf()); | ||
x[1] = Math.max(extent[1],x[1].valueOf()); | ||
return d; | ||
}); | ||
var y = 0; | ||
charts.forEach(function(svg,i) { | ||
var h = svg.weight / totalWeight * height; | ||
svg = d3.select(svg); | ||
svg.attr({x:0,y:y,height:h}); | ||
y += h; | ||
var chart = dpl.chart(svg); | ||
chart.scale('x').domain(x); | ||
chart.on('resize.setX',function() { | ||
chart.scale('x').domain([x[0], x[1]]); | ||
}); | ||
chart.on('render.autobbox',function() {}); | ||
var text = svg.select('.chart.title').text(); | ||
svg.selectAll('.title').remove(); | ||
chart.setTitle('y2',text); | ||
svg.select('.y2.title').style('font-size','90%'); | ||
chart.axis('y').ticks(5); | ||
chart.on("render.autobbox",function() {}); | ||
chart.on('render.autoviewbox',function() {}); | ||
//if (i < charts.length-1) svg.selectAll('.x.axis text').remove() | ||
chart.render(); | ||
//if (chart.tickFormat('y')) chart.tickFormat('y2',d3.format('.2s')) | ||
if (i < charts.length-1) { | ||
chart.margin({left:50,right:5,bottom:3,top:3}); | ||
svg.selectAll('.x.axis').selectAll('text').remove(); | ||
} else { | ||
chart.margin({left:50,right:5,bottom:20,top:3}); | ||
} | ||
//svg.select('.y.title').style('font-size','70%') | ||
//chart.margin({bottom:1}); | ||
chart.render(); | ||
svg.attr('viewBox',null); | ||
}); | ||
timeline.forEach(function(d,i) { | ||
if (d.CHART) { | ||
var g = d3.select(self) | ||
.selectAll('[data-reptile="'+d.CHART+'"]').select('.graph') | ||
.append('g'); | ||
if (d.Y) g.datum({x:new Date(d.DT),y:d.Y}); | ||
else g.datum({x:new Date(d.DT),y:Math.random()}).attr('data-scale-y','ch'); | ||
g.call(function(g) { | ||
g.append('circle').attr('r',10).style({fill:'white',stroke:'black','fill-opacity':0.8}); | ||
g.append('text').text(i+1).attr({dy:'0.4em','text-anchor':'middle'}); | ||
}) | ||
.call(dpl.render); | ||
} | ||
}); | ||
}) | ||
} | ||
; | ||
/* jshint multistr:true */ | ||
reptile.timeFormat = d3.time.format.multi([ | ||
["%b %d", function(d) { return d.getDate() != 1; }], | ||
["%b", function(d) { return d.getMonth(); }], | ||
["%Y", function() { return true; }] | ||
]); | ||
reptile.baseChart = function(svg) { | ||
if (svg.node().tagName !== 'svg') svg = svg.append('svg'); | ||
svg.style({height: "100%",width: "100%"}); | ||
var chart = dpl.chart(svg) | ||
.showAxes(['x','y']) | ||
.scale("x",d3.time.scale()) | ||
.tickFormat('x',reptile.timeFormat) | ||
.margin(10) | ||
.on('render.autoviewbox',function() { | ||
var svg = this.g, | ||
bbox = svg.node().getBBox(); | ||
if (!svg.attr('data-static')) | ||
svg.attr("viewBox",bbox.x+" "+bbox.y+" "+bbox.width+" "+bbox.height); | ||
}); | ||
angular.element(window).on('resize',function() { | ||
chart.render(); | ||
}); | ||
return chart; | ||
}; | ||
})(); |
var express = require('express'), | ||
bodyParser = require('body-parser'), | ||
compression = require('compression'), | ||
serveStatic = require('serve-static'), | ||
Promise = require('bluebird'); | ||
@@ -18,12 +21,31 @@ | ||
"c" : function(b) { | ||
int('a') | ||
console.log('error') | ||
throw 'this is an error' | ||
}, | ||
"d" : function(test) { | ||
return test+3; | ||
}, | ||
"e" : function(input) { | ||
return input * 10 | ||
} | ||
} | ||
var ec = require('./express-clues')(api,{safe:true}) | ||
express() | ||
.get('/api/:fn',require('./express-clues')(api)) | ||
//.use(compression()) | ||
.use(bodyParser.json()) | ||
.get('/api/:fn',ec()) | ||
.post('/api/:fn',ec()) | ||
.get('/list',ec('a,b,c')) | ||
.get('/test',function(req,res) {res.sendFile('/clues/util/test.html')}) | ||
.use(serveStatic('./')) | ||
.use(serveStatic('../')) | ||
.listen(3008); | ||
console.log('listening on 3008') | ||
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
228818
25
5821
1
149
6
11