class Xapixctl::TitanCli

Constants

DEFAULT_METHODS

Public Instance Methods

service(akoios_url) click to toggle source
# File lib/xapixctl/titan_cli.rb, line 27
def service(akoios_url)
  url = URI.parse(akoios_url)
  schema = JSON.parse(RestClient.get(File.join(url.to_s, 'spec'), params: { host: url.hostname }, headers: { accept: :json }))
  patch_schema(schema)
  connector_refs = import_swagger(File.basename(url.path), schema)
  say "\n== Onboarding Connectors", :bold
  connectors = match_connectors_to_action(connector_refs)
  if connectors.empty?
    warn "\nNo valid connectors for ML service detected, not building service."
    exit 1
  end
  say "\n== Building Service", :bold
  service_doc = build_service(schema.dig('info', 'title'), connectors)
  res = prj_connection.apply(service_doc)
  say "\ncreated / updated service #{res.first}"
end

Private Instance Methods

build_service(title, connectors) click to toggle source
# File lib/xapixctl/titan_cli.rb, line 124
def build_service(title, connectors)
  {
    version: 'v1',
    kind: 'Service',
    metadata: { id: title.parameterize },
    definition: {
      name: title.humanize,
      actions: connectors.map { |action, connector| build_service_action(action, connector) }
    }
  }
end
build_service_action(action_type, connector) click to toggle source
# File lib/xapixctl/titan_cli.rb, line 136
def build_service_action(action_type, connector)
  {
    name: action_type,
    parameter_schema: parameter_schema(action_type, connector),
    result_schema: result_schema(action_type),
    pipeline: { units: pipeline_units(action_type, connector) }
  }
end
entry_unit() click to toggle source
# File lib/xapixctl/titan_cli.rb, line 187
def entry_unit
  { version: 'v2', kind: 'Unit/Entry',
    metadata: { id: 'entry' },
    definition: { name: 'Entry' } }
end
extract_leafs(schema, current_path = []) click to toggle source
# File lib/xapixctl/titan_cli.rb, line 274
def extract_leafs(schema, current_path = [])
  return unless schema
  case schema['type']
  when 'object'
    schema['properties'].flat_map { |key, sub_schema| extract_leafs(sub_schema, current_path + [key]) }.compact
  when 'array'
    extract_leafs(schema['items'], current_path + [:[]])
  else
    { path: current_path, node: schema }
  end
end
extract_schema(data_sample) click to toggle source
# File lib/xapixctl/titan_cli.rb, line 111
def extract_schema(data_sample)
  case data_sample
  when Array
    { type: 'array', items: extract_schema(data_sample[0]) }
  when Hash
    { type: 'object', properties: data_sample.transform_values { |v| extract_schema(v) } }
  when Numeric
    { type: 'number' }
  else
    {}
  end
end
import_swagger(filename, schema) click to toggle source
# File lib/xapixctl/titan_cli.rb, line 65
def import_swagger(filename, schema)
  Tempfile.create([filename, '.json']) do |f|
    f.write(schema.to_json)
    f.rewind

    if options[:schema_import]
      result = prj_connection.update_schema_import(options[:schema_import], f)
      say "updated #{result.dig('resource', 'kind')} #{result.dig('resource', 'id')}"
    else
      result = prj_connection.add_schema_import(f)
      say "created #{result.dig('resource', 'kind')} #{result.dig('resource', 'id')}"
    end
    result.dig('schema_import', 'updated_resources')
  end
end
match_connectors_to_action(connector_refs) click to toggle source
# File lib/xapixctl/titan_cli.rb, line 81
def match_connectors_to_action(connector_refs)
  connector_refs.map do |connector_ref|
    connector = prj_connection.resource(connector_ref['kind'], connector_ref['id'])
    action = DEFAULT_METHODS[connector.dig('definition', 'name')]
    say "\n#{connector['kind']} #{connector.dig('definition', 'name')} -> "
    if action
      say "#{action} action"
      updated_connector = update_connector_with_preview(connector)
      [action, updated_connector] if updated_connector
    else
      say "no action type detected, ignoring"
      nil
    end
  end.compact
end
parameter_schema(action_type, connector) click to toggle source
# File lib/xapixctl/titan_cli.rb, line 145
def parameter_schema(action_type, connector)
  case action_type
  when :predict
    { type: 'object', properties: {
      data: connector.dig('definition', 'parameter_schema', 'properties', 'body', 'properties', 'data'),
    } }
  when :performance
    { type: 'object', properties: {} }
  end
end
patch_schema(schema) click to toggle source
# File lib/xapixctl/titan_cli.rb, line 46
def patch_schema(schema)
  predict_schema = schema.dig('paths', '/predict', 'post')
  if predict_schema
    predict_data = JSON.parse(options[:data]) rescue {}
    predict_schema['operationId'] = 'predict'
    predict_schema['parameters'].each do |param|
      if param['name'] == 'json' && param['in'] == 'body'
        param['schema']['properties'] = { "data" => extract_schema(predict_data) }
        param['schema']['example'] = { "data" => predict_data }
      end
    end
  end

  performane_schema = schema.dig('paths', '/performance', 'get')
  if performane_schema
    performane_schema['operationId'] = 'performance'
  end
end
performance_result_unit() click to toggle source
# File lib/xapixctl/titan_cli.rb, line 244
def performance_result_unit
  { version: 'v2', kind: 'Unit/Result',
    metadata: { id: 'result' },
    definition: {
      name: 'Result',
      inputs: ['performance'],
      formulas: [{
        ref: "#performance.recall",
        formula: "WITH(data, JSON.DECODE(REGEXREPLACE(performance.body, \"'\", \"\\\"\")), .data.recall)"
      }, {
        ref: "#performance.accuracy",
        formula: "WITH(data, JSON.DECODE(REGEXREPLACE(performance.body, \"'\", \"\\\"\")), .data.accuracy)"
      }, {
        ref: "#performance.precision",
        formula: "WITH(data, JSON.DECODE(REGEXREPLACE(performance.body, \"'\", \"\\\"\")), .data.precision)"
      }, {
        ref: "#performance.min_acc_threshold",
        formula: "WITH(data, JSON.DECODE(REGEXREPLACE(performance.body, \"'\", \"\\\"\")), .data.min_acc_threshold)"
      }],
      parameter_sample: {
        "performance" => {
          "recall" => 0.8184713375796179,
          "accuracy" => 0.9807847896440129,
          "precision" => 0.8711864406779661,
          "min_acc_threshold" => 0.84,
        }
      }
    } }
end
performance_unit(connector) click to toggle source
# File lib/xapixctl/titan_cli.rb, line 234
def performance_unit(connector)
  { version: 'v2', kind: 'Unit/DataSource',
    metadata: { id: 'performance' },
    definition: {
      name: 'Performance',
      inputs: ['entry'],
      data_source: connector.dig('metadata', 'id')
    } }
end
pipeline_units(action_type, connector) click to toggle source
# File lib/xapixctl/titan_cli.rb, line 178
def pipeline_units(action_type, connector)
  case action_type
  when :predict
    [entry_unit, predict_unit(connector), predict_result_unit]
  when :performance
    [entry_unit, performance_unit(connector), performance_result_unit]
  end
end
predict_result_unit() click to toggle source
# File lib/xapixctl/titan_cli.rb, line 207
def predict_result_unit
  { version: 'v2', kind: 'Unit/Result',
    metadata: { id: 'result' },
    definition: {
      name: 'Result',
      inputs: ['predict'],
      formulas: [{
        ref: "#prediction.percent",
        formula: "IF(.predict.status = 200, ROUND(100*coerce.to-float(.predict.body), 2))"
      }, {
        ref: "#prediction.raw",
        formula: "IF(.predict.status = 200, coerce.to-float(.predict.body))"
      }, {
        ref: "#success",
        formula: ".predict.status = 200"
      }, {
        ref: "#error",
        formula: "IF(.predict.status <> 200, 'Model not trained!')"
      }],
      parameter_sample: {
        "prediction" => { "percent" => 51.12, "raw" => 0.5112131 },
        "success" => true,
        "error" => nil
      }
    } }
end
predict_unit(connector) click to toggle source
# File lib/xapixctl/titan_cli.rb, line 193
def predict_unit(connector)
  leafs = extract_leafs(connector.dig('definition', 'parameter_schema', 'properties', 'body'))
  { version: 'v2', kind: 'Unit/DataSource',
    metadata: { id: 'predict' },
    definition: {
      name: 'Predict',
      inputs: ['entry'],
      data_source: connector.dig('metadata', 'id'),
      formulas: leafs.map { |leaf|
        { ref: leaf[:node]['$id'], formula: (['', 'entry'] + leaf[:path]).join('.') }
      }
    } }
end
result_schema(action_type) click to toggle source
# File lib/xapixctl/titan_cli.rb, line 156
def result_schema(action_type)
  case action_type
  when :predict
    { type: 'object', properties: {
      prediction: { type: 'object', properties: { percent: { type: 'number' }, raw: { type: 'number' } } },
      success: { type: 'boolean' },
      error: { type: 'string' }
    } }
  when :performance
    { type: 'object', properties: {
      performance: {
        type: 'object', properties: {
          accuracy: { type: 'number' },
          precision: { type: 'number' },
          recall: { type: 'number' },
          min_acc_threshold: { type: 'number' }
        }
      }
    } }
  end
end
update_connector_with_preview(connector) click to toggle source
# File lib/xapixctl/titan_cli.rb, line 97
def update_connector_with_preview(connector)
  say "fetching preview for #{connector['kind']} #{connector.dig('definition', 'name')}..."
  preview_details = prj_connection.data_source_preview(connector.dig('metadata', 'id'))
  preview = preview_details.dig('preview', 'sample')
  say "got a #{preview['status']} response: #{preview['body']}"
  if preview['status'] != 200
    say "unexpected status, please check data or model"
  elsif yes?("Does this look alright?", :bold)
    res = prj_connection.accept_data_source_preview(connector.dig('metadata', 'id'))
    return res.dig('data_source', 'resource_description')
  end
  nil
end