tpmkms
Version:
Reusable libraries for Entodicton, a natural language to json converter
636 lines (603 loc) • 22.5 kB
JavaScript
const { knowledgeModule, where } = require('./runtime').theprogrammablemind
const { defaultContextCheck, toFinalValue } = require('./helpers')
const currencyKM = require('./currency.js')
const events = require('./events.js')
const math = require('./math.js')
const helpKM = require('./help.js')
const { propertyToArray, wordNumber, toEValue } = require('./helpers')
const { table } = require('table')
const _ = require('lodash')
const reports_tests = require('./reports.test.json')
const instance = require('./reports.instance.json')
const { v4 : uuidv4, validate : validatev4 } = require('uuid');
const template ={
configs: [
"price and quantity are properties",
"worth means price times quantity"
],
}
/*
show supplier on report1
show supplier (on current report)
NEXT reportAction on report where remove/move etc are reportActions
delete means remove remove column 2 show it
show the worth
*/
/* START USER DEFINED: this part could be calling your backend db */
function compareValue(property, v1, v2) {
return v1[property] < v2[property] ? -1 :
v1[property] > v2[property] ? 1 :
0;
}
function newReport({km, objects}) {
objects.tempReportId += 1
const reportId = `tempReport${objects.tempReportId}`
km('stm').api.remember({ context: { marker: "report", text: reportId, types: [ "report" ], value: reportId, word: reportId } })
// name to listing
objects.listings[reportId] = {
columns: ['name', 'supplier'],
ordering: [],
type: 'tables',
}
return reportId
}
function compareObject(ordering) {
return (v1, v2) => {
for (const order of ordering) {
c = compareValue(order[0], v1, v2)
if (c == 0) {
continue
}
if (order[1] == 'descending') {
c *= -1
}
return c
}
return 0
}
}
function sort({ ordering, list }) {
return [...list].sort(compareObject(ordering))
}
const testData = {
name: 'clothes',
types: [ 'pants', 'shorts' ],
products: [
{ marker: 'clothes', supplier: "x industries", isInstance: true, type: 'pants', name: 'pants1', price: 9, id: 1, quantity: 4 },
{ marker: 'clothes', supplier: "y industries", isInstance: true, type: 'shirt', name: 'shirt1', price: 15, id: 2, quantity: 6 },
],
}
const testData2 = {
name: 'models',
types: [ 'tanks', 'planes' ],
products: [
{ marker: 'models', supplier: "tamiya", isInstance: true, type: 'tanks', name: 'tiger', price: 9, id: 1, quantity: 3 },
{ marker: 'models', supplier: "dragon", isInstance: true, type: 'planes', name: 'spitfire', price: 15, id: 2, quantity: 7 },
]
}
function apiTemplate(marker, testData) {
return {
getName: () => testData.name,
getTypes: () => testData.types,
getAllProducts: ({ columns, ordering }) => sort({ ordering, list: testData.products }),
getByTypeAndCost: ({type, cost, comparison}) => {
results = []
testData.forEach( (product) => {
if (product.type == type && comparison(product.cost)) {
results.push(product)
}
})
return results
},
productGenerator: {
match: ({context}) => context.marker == marker && context.isInstance,
apply: ({context}) => `${context.name}`,
}
}
}
/* END USER DEFINED: this part could be calling your backend db */
const api1 = apiTemplate('models', testData2)
const api2 = apiTemplate('clothes', testData)
const config = {
name: 'reports',
operators: [
//"(([type]) [([(<less> ([than]))] ([amount]))])",
//"(([show] ([listingType|])) <([for] (<the> ([listings])))>)",
"([listAction|list] (<the> ([product|products])))",
//"what can you report on",
//"([list] ((<the> (([product|products]))) <(<that> ([cost] ([price])))>)) )"
"([reportAction|] ([on] ([report|])))",
"(([product]) <(<that> ([cost] ([price])))>)",
"([answer] ([with] ([listingType|])))",
"([show] (<the> ([property|property,properties])))",
"([call] ([report|]) (rest)*)",
// "(([property]) <ascending>)",
// "(([property]) <descending>)",
"([describe] ([report]))",
"([price])",
"([quantity])",
"([ordering])",
"([show:reportBridge] ([report]))",
"([column] ([number]))",
"([move] ([column]) ([to] ([column])))",
"([move:directionBridge] ([column]) ([direction]))",
"([left])",
"([right])",
"([remove] ([column]))",
/*
call this the banana report
show the banana report
price descending
price ascending
*/
// DONE show price and quantity
// DONE describe report1
// DONE after the report changes show it
// DONE after changing a report show it -> event IDEA OF EVENT
// after changing report 1
// after changing the columns
// after move show the report -> marker
// -> multi word report names
// call this report a show report a show report a for products that code more than 10 dollars
// DONE show the models
// save this as report1 / show report1
// list the products with the price descending
// call this report report1
// show the report before
// show report1 with price descending
// column 2 / the price column
// left / left 2 / to the left / to the left 2
// move [column] to [column]
// move [column] [direction]
// move [column] to [direction]
// move [column] before/after [column]
// move the price column before the name column
// move column 2 left
// move column 2 left 2
// move column 2 to 1
// move price before name
// move price to the far right
// move column 2 to column 3
// call it report1 move column 2 to column 3 show it
// show the price times quantity call it worth
// call the columns fred's amount
// worth means quanity times price
// show the price in euros
// show the cost <-> price
// the price columns two to the left / to the far rigth
// show price as column 3
// call this report report1
],
bridges: [
{
where: where(),
id: "move",
level: 0,
bridge: "{ ...next(operator), on: { marker: 'report', pullFromContext: true }, from: after[0], to: after[1] }",
directionBridge: "{ ...next(operator), on: { marker: 'report', pullFromContext: true }, directionBridge: true, from: after[0], to: after[1] }",
generatorp: async ({context, gp}) => `move ${await gp(context.from)} ${await gp(context.to)}`,
semantic: async ({context, e, objects, kms, insert}) => {
const report = await e(context.on)
const id = report.value.value
const listing = objects.listings[id]
const from = context.from.index.value;
let to;
if (context.directionBridge) {
if (context.to.marker == 'left') {
to = from - 1
}
if (context.to.marker == 'right') {
to = from + 1
}
} else {
to = context.to.toObject.index.value;
}
const old = listing.columns[from-1]
listing.columns[from-1] = listing.columns[to-1]
listing.columns[to-1] = old
kms.events.api.happens(insert, { marker: "changes", level: 1, changeable: report })
}
},
{
where: where(),
id: "remove", level: 0,
bridge: "{ ...next(operator), on: { marker: 'report', pullFromContext: true }, removee: after[0] }",
generatorp: async ({context, gp}) => `remove ${await gp(context.removee)}`,
semantic: async ({context, e, kms, insert, objects}) => {
const report = await e(context.on)
const id = report.value.value
const listing = objects.listings[id]
const column = context.removee.index.value
listing.columns.splice(column-1, 1)
kms.events.api.happens(insert, { marker: "changes", level: 1, changeable: report })
}
},
{
where: where(),
id: "column", level: 0,
bridge: "{ ...next(operator), index: after[0] }",
generatorp: async ({context, gp}) => `column ${await gp(context.index)}`,
},
{ id: "ordering", level: 0, bridge: "{ ...next(operator) }" },
{ id: "direction", level: 0, bridge: "{ ...next(operator) }" },
{ id: "left", isA: ['direction'], level: 0, bridge: "{ ...next(operator) }" },
{ id: "right", isA: ['direction'], level: 0, bridge: "{ ...next(operator) }" },
{
where: where(),
id: "report", level: 0,
isA: ['theAble'],
words: [{word: "reports", number: "many"}],
bridge: "{ ...next(operator) }",
generators: [
{
where: where(),
match: ({context}) => context.marker == 'report' && context.describe,
apply: async ({context, apis, gs, objects}) => {
const listings = objects.listings[context.value]
// {"type":"tables","columns":["name"],"ordering":[]}
return `for ${listings.api}, showing the ${wordNumber('property', listings.columns.length > 1)} ${await gs(listings.columns, ' ', ' and ')} as ${listings.type}`
}
},
{
where: where(),
match: ({context}) => context.marker == 'report' && context.evalue,
apply: ({context}) => context.evalue.value
}
],
},
// { id: "ascending", level: 0, bridge: "{ ...before[0], ordering: 'ascending' }" },
// { id: "descending", level: 0, bridge: "{ ...before[0], ordering: 'descending', modifiers: append(['ordering'], before[0].modifiers) }" },
{ id: "product", level: 0, bridge: "{ ...next(operator) }" },
{ id: "listAction", level: 0, bridge: "{ ...next(operator), what: after[0]}" },
{ id: "on", level: 0, bridge: "{ ...next(operator), report: after[0] }" },
{ id: "reportAction", level: 0, bridge: "{ ...next(operator), on: after[0].report }" },
{ id: "that", level: 0, bridge: "{ ...*, constraint: context }" },
{ id: "cost", level: 0, bridge: "{ ...next(operator), price: after[0] }" },
{ id: "cost", level: 1, bridge: "{ ...squish(operator), thing*: before[0] }" },
{ id: "property", level: 0, bridge: "{ ...next(operator) }" },
{ id: "price", level: 0,
isA: ['number', 'property'],
bridge: "{ ...next(operator) }" },
{ id: "quantity", level: 0,
isA: ['number', 'property'],
bridge: "{ ...next(operator) }" },
{ id: "listingType", level: 0, bridge: "{ ...next(operator) }" },
{ id: "with", level: 0, bridge: "{ ...next(operator), type: after[0].value }" },
{ id: "answer", level: 0, bridge: "{ ...next(operator), type: after[0].type }" },
{
id: "show",
level: 0,
localHierarchy: [['unknown', 'property']],
bridge: "{ ...next(operator), on: { 'marker': 'report', types: ['report'], pullFromContext: true }, properties: after[0] }",
reportBridge: "{ ...next(operator), report: after[0] }"
},
{
where: where(),
id: "describe",
level: 0,
isA: ['verb'],
bridge: "{ ...next(operator), report: after[0] }",
"generatorp": async ({g, context}) => `describe ${await g(context.report)}`,
"generatorr": async ({gp, context, apis, objects, config}) => {
const reports = propertyToArray(context.report)
let response = ''
for (const report of reports) {
if (report.number == 'many') {
for (const reportId in objects.listings) {
if (reportId.startsWith('tempReport')) {
continue
}
const description = {describe: true, word: reportId, types:["report"], value: reportId, text: reportId, marker: "report"}
response += `${reportId}: ${await gp(description)}\n`
}
} else {
// response += `${gp(report)}: ${describe(report.value)}\n`
response += `${await gp(report)}: ${await gp({ ...report, describe: true })}\n`
}
}
return response
},
semantic: ({context}) => {
context.isResponse = true
}
},
{
where: where(),
id: "call",
level: 0,
bridge: "{ ...next(operator), namee: after[0], name: after[1:][0] }",
separators: " ?",
generatorp: async ({g, gs, context}) => {
return `call ${await g(context.namee)} ${await gs(context.name)}`
},
semantic: async ({context, objects, e, config, km}) => {
const namee = (await e(context.namee)).evalue
const id = namee.value
const listing = objects.listings[id]
// const name = context.name.text
const name = context.name.map((n) => n.text).join('')
objects.listings[name] = {...listing}
config.addWord(`${name}`, { id: 'report', initial: `{ value: "${name}" }` })
km('stm').api.remember({
context: {
marker: "report",
text: name,
types: [ "report" ],
value: id,
word: name
}
})
}
},
],
hierarchy: [
['property', 'comparable'],
['ascending', 'ordering'],
['descending', 'ordering'],
['property', 'theAble'],
['column', 'toAble'],
['report', 'it'],
['report', 'this'],
['describe', 'verb'],
['call', 'verb'],
['show', 'verb'],
['report', 'changeable'],
['show', 'action'],
['move', 'reportAction'],
['remove', 'reportAction'],
['mathematicalExpression', 'property'],
/*
['timesExpression', 'property'],
['plusExpression', 'property'],
['minusExpression', 'property'],
['divideByExpression', 'property'],
*/
// ['report', 'product'],
],
floaters: ["api", "isQuery"],
debug: true,
"version": '3',
"words": {
"literals": {
"tables": [{"id": "listingType", "initial": "{ value: 'tables' }" }],
"sentences": [{"id": "listingType", "initial": "{ value: 'sentences' }" }],
}
},
priorities: [
{ "context": [['ordering', 0], ['article', 0], ], "choose": [0] },
],
associations: {
positive: [
{ context: [['show', 0], ['article', 0], ['property', 2]], choose: { index: 0, increment: true } },
{ context: [['show', 0], ['thisitthat', 0]], choose: { index: 0, increment: true } },
]
},
generators: [
{
notes: 'paraphrase show',
where: where(),
match: ({context, objects}) => context.marker == 'show' && context.paraphrase,
apply: async ({gs, gp, e, apis, objects, context}) => {
if (context.report) {
return `show ${await gp(context.report)}`
} else {
const report = await e(context.on)
const id = report.value.value
const listing = objects.listings[id]
return `the properties being shown are ${await gs(listing.columns, ', ', ' and ')}`
}
}
},
{
where: where(),
match: ({context}) => context.marker == 'product' && !context.isInstance,
apply: ({context}) => `the ${context.word}`
},
{
where: where(),
match: ({context}) => context.marker == 'listAction' && context.paraphrase,
apply: async ({g, context}) => `list ${await g(context.what)}`
},
{
notes: 'show the results as a sentence',
where: where(),
match: ({context, objects}) => {
if (!(context.marker == 'listAction' && context.isResponse)) {
return false
}
if (objects.listings[context.id].type == 'sentences') {
return true
}
},
apply: async ({g, gs, context, objects}) => {
const listing = objects.listings[context.id]
return `the ${await g(listing.api)} are ${await gs(context.listing, ' ', ' and ')}`
}
},
{
notes: 'show the results as a table',
where: where(),
match: ({context, objects}) => {
if (!(context.marker == 'listAction' && context.isResponse && !context.paraphrase)) {
return false
}
if (objects.listings[context.id].type == 'tables') {
return true
}
},
apply: async ({objects, context, e, kms}) => {
let report = '';
const products = context.listing
const columns = objects.listings[context.id].columns
if (false) {
kms.stm.api.setVariable('price', { marker: 'price', value: 23 })
kms.stm.api.setVariable('quantity', { marker: 'quantity', value: 3 })
const c1 = { marker: 'worth', value: 'worth' }
r1 = toEValue(await e(c1));
r2 = await e({ marker: 'supplier', value: 'supplier' })
}
const data = []
for (const product of products) {
const row = []
for (const p of Object.keys(product)) {
kms.stm.api.setVariable(p, { marker: p, value: product[p] })
}
for (const property of columns) {
const value = toFinalValue(await e({ marker: property, value: property }));
row.push(value)
}
data.push(row)
};
report += table([columns].concat(data))
return report
}
},
{
where: where(),
match: ({context}) => context.marker == 'answer' && context.paraphrase,
apply: ({context}) => `answer with ${context.type}`
},
{
where: where(),
match: ({context}) => context.marker == 'answer' && !context.paraphrase,
apply: ({context}) => `answering with ${context.type}`
},
],
semantics: [
{
where: where(),
notes: 'handle show semantic',
match: ({context}) => context.marker == 'show',
apply: async ({context, e, km, kms, apis, insert, config, objects}) => {
if (context.report) {
const values = propertyToArray(context.report)
const responses = []
for (let value of values) {
if (!value.value || value.pullFromContext) {
value = await e(value)
}
let id = value.value
if (value.evalue) {
id = value.evalue.value || value.evalue
}
const listing = objects.listings[id]
const api = apis[listing.api]
responses.push({
marker: 'listAction',
// listing: config._api.apis[value.value.api].getAllProducts(api.listings[value.value.id]),
listing: api.getAllProducts(listing),
id,
isResponse: true,
})
}
context.evalue = {
marker: 'list',
newLinesOnly: true,
value: responses,
}
context.isResponse = true
} else {
const report = await e(context.on)
const id = report.value.value
const listing = objects.listings[id]
const values = propertyToArray(context.properties)
for (const value of values) {
let column = value.marker
if (value.marker == 'unknown') {
column = value.value
}
if (!listing.columns.includes(column)) {
listing.columns.push(column)
}
listing.ordering.push([value.marker, value.ordering])
listing.columns = _.uniq(listing.columns)
}
kms.events.api.happens(insert, { marker: "changes", level: 1, changeable: report })
}
}
},
{
notes: 'get the report data',
where: where(),
match: ({context}) => context.marker == 'listAction',
apply: async ({context, e, objects, apis, km, config}) => {
//const name = '***current***'
if (context.api) {
// id = newReport({km, objects})
const report = await e({ marker: 'report', pullFromContext: true })
const id = report.value.value
const listing = objects.listings[id]
listing.api = context.api
// TODO change this to context.data
context.id = id
context.listing = apis[listing.api].getAllProducts(listing)
} else {
const report = await e({ marker: 'report', pullFromContext: true })
const id = report.evalue.value
const listing = objects.listings[id]
const api = apis[listing.api]
context.listing = api.getAllProducts(listing)
context.id = id
}
context.isResponse = true
},
},
{
match: ({context}) => context.marker == 'answer',
apply: async ({e, context, objects, kms, insert}) => {
const report = await e({ marker: 'report', pullFromContext: true })
const id = report.value.value
const listing = objects.listings[id]
listing.type = context.type
kms.events.api.happens(insert, { marker: "changes", level: 1, changeable: { marker: 'report', pullFromContext: true } })
}
},
],
};
function initializeApi(config, api) {
const type = api.getName();
config.addWord(type, {"id": "product", "initial": "{ value: '" + type + `', api: '${type}'}` })
/*
api.listing = {
api: type,
type: 'tables',
columns: ['name', 'supplier'],
ordering: [],
}
*/
// newReport(config, api)
config.addGenerator( api.productGenerator )
// const open = '{'
// const close = '}'
// config.addWord(type, {"id": "report", "initial": `${open} value: '${type}' ${close}` })
}
async function initializer({config, objects, km, kms, isModule}) {
if (!isModule) {
await kms.reports.addAPI(api1)
await kms.reports.addAPI(api2)
}
objects.tempReportId = 0
objects.listings = {
}
const id = newReport({km, objects})
if (!isModule) {
objects.listings[id].api = 'clothes'
}
}
knowledgeModule({
config,
includes: [currencyKM, helpKM, math, events],
multiApiInitializer: initializeApi,
initializer,
module,
description: 'this module is for getting info about a concept with properties',
test: {
name: './reports.test.json',
contents: reports_tests,
checks: {
context: [defaultContextCheck({ extra: ['name', 'index'] })],
},
},
instance,
template,
})