You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 14 Next »

Everything within the C3 AI Suite is managed through or exists as a C3.ai Type. C3.ai Types allow users to describe, process and interact with data and analytics. Broadly speaking, C3.ai Types are like Java classes, and include 'fields', 'methods', and an inheritance structure. Once you understand the structure of C3.ai Type, you will be able to write C3.ai Types to load data, run analytics, build and deploy machine learning models, and configure application logic, for your research project. 

All C3.ai Types are defined in a .c3typ file, stored in 'src' directory of a C3.ai Package. (We'll get to C3.ai Packages a little later). A .c3typ file can only define a single C3.ai Type. The .c3type file name must match the name of the C3.ai Type being defined. 

First, we will describe the C3.ai Package, to download for this tutorial. Then, we will discuss the syntax, special keywords, fields, methods, and inheritance structure of C3.ai Types. Finally, we will review some examples.

lightbulbAD C3 Package

In this tutorial, we will use the lightbulbAD C3.ai package. To download the source code for this package, please follow the instructions available here: C3 lightbulbAD Package

Syntax

 To help familiarize yourself with the syntax for a C3.ai Type, let's look at how the 'SmartBulb' Type is defined in the lightbulbAD C3.ai Package:

/*
 * Copyright 2009-2020 C3 (www.c3.ai). All Rights Reserved.
 * This material, including without limitation any software, is the confidential trade secret and proprietary
 * information of C3 and its licensors. Reproduction, use and/or distribution of this material in any form is
 * strictly prohibited except as set forth in a written license agreement with C3 and/or its authorized distributors.
 * This material may be covered by one or more patents or pending patent applications.
 */

/**
 * A single light bulb capable of measuring various properties, including power consumption, light output, etc.
 */
entity type SmartBulb extends LightBulb mixes MetricEvaluatable, NLEvaluatable type key "SMRT_BLB" {

  /**
   * This bulb's historical measurements.
   */
  bulbMeasurements: [SmartBulbMeasurementSeries](smartBulb)

   /**
    * This bulb's historical predictions.
    */
   @db(order='descending(timestamp)')
   bulbPredictions: [SmartBulbPrediction](smartBulb)

   /**
    * This bulb's latest prediction.
    */
   currentPrediction: SmartBulbPrediction stored calc "bulbPredictions[0]"

   /**
    * This bulb's historical events.
    */
   bulbEvents: [SmartBulbEvent](smartBulb)

   /**
    * The latitude of this bulb.
    */
   latitude: double

   /**
    * The longitude of this bulb.
    */
   longitude: double

   /**
    * The unit of measurement used for this bulb's light output measurements.
    */
   lumensUOM: Unit

   /**
    * The unit of measurement used for this bulb's power consumption measurements.
    */
   powerUOM: Unit

   /**
    * The unit of measurement used for this bulb's temperature measurements.
    */
   temperatureUOM: Unit

   /**
    * The unit of measurement used for this bulb's voltage measurements.
    */
   voltageUOM: Unit

   /**
    * A SmartBulb is associated to a {@link Fixture} through a SmartBulbToFixtureRelation.
    */
   @db(order='descending(start), descending(end)')
   fixtureHistory: [SmartBulbToFixtureRelation](from)

   /**
    * The current Fixture to which this bulb is attached.
    */
   currentFixture: Fixture stored calc 'fixtureHistory[0].(end == null).to'

   /**
    * Method to determine the expected lumens of a light bulb
    */
   expectedLumens: function(wattage: !decimal, bulbType: !string): double js server

   /**
    * Returns the life span of this smartBulb
    */
   lifeSpanInYears: function(bulbId: string): double js server

   /**
    * Returns the average life span of all smartBulbs.
    */
   averageLifeSpan: function(): double js server

   /**
    * Returns the id of the smart bulb with the shortest recorded life span to date.
    */
   shortestLifeSpanBulb: function(): string js server

   /**
    * Returns the id of the smart bulb with the longest recorded life span to date.
    */
   longestLifeSpanBulb: function(): string js server

}

At a glance, a .c3typ file has the following components: 

  • Keywords, which define the name, inheritance, and properties of a C3.ai Type
  • Fields, which define attributes or data elements on a C3.ai Type
  • Methods, which define business logic on C3.ai Types
  • Annotations like '@db', which often precede fields or methods, and configure the behavior of a C3.ai type
  • Constants such as strings or integers.
  • Comments which aren't parsed by C3, and instead provide a message to the developer.

At a high level, the basic syntax to define C3.ai Type is as follows (originally from official C3.ai Type documentation here: https://developer.c3.ai/docs/7.12.17/topic/mda-types

[remix, extendable] [entity] type TypeName [extends, mixes AnotherType] {
	/* comments */
	[field declaration]
	[method declaration]
}

Everything within square brackets '[]' is optional.

C3.ai Resources on C3.ai Types:

Keywords

A C3.ai Type definition is introduced with a series of keywords. These keywords tell the C3 AI Suite how to construct this type, store it internally, and whether it inherits fields and methods from other already defined C3.ai Types. We describe each:

  • type: All .c3typ files have the keyword 'type'. This keyword tells the C3 AI Suite that this file defines a C3.ai Type. 
  • entity: The keyword 'entity' keyword indicates the C3.ai Type is a 'Persistable' (i.e., needs to be stored in a database in the C3 AI Suite). Since a large majority of C3.ai Types are Persistable, 'entity' is an important keyword to include in .c3typ files. 
  • mixes: Adding the keyword `mixes AnotherType`, a C3.ai Type inherits the properties (e.g., fields, methods) of `AnotherType'. A C3.ai Type can mix-in multiple Types. 
  • remixes: Adding the keyword`remix AType`, allows C3.ai developer to modify an existing C3.ai Type (e.g., add new fields or methods, update existing fields or methods). Re-mixing is useful when you don't have access to the original .c3type file for a particular C3.ai Type, but wish to edit that C3.ai Type. 
  • extends: C3.ai developers often add the keyword `extends AnotherType`, to define a subclass of a particular C3.ai Type (e.g., SmartBulb extends Lightbulb). The extension Type (i.e., Smartbulb) inherits all the fields and methods of the original Type (i.e., Lightbulb). Additionally, data associated with the extension and original C3.ai Types are stored in the same database table. Therefore, you must specify a `type key`(on the extension Type), so the C3 AI Suite can distinguish data associated with the extension and original C3.ai Types. Please note, a C3.ai Type can only extend ONE other Type. 
  • extendable: All extended Types (i.e., Lightbulb) MUST be marked with the keyword "extendable".  Under the covers, the keyword "extendable" tells the C3 AI Suite to add a field (called 'key') to the database table, storing the extended (or base) C3.ai Type. This 'key' field is used to distinguish data associated with different varieties of the extended C3.ai Type.
  • type key: All extension types (i.e., Smartbulb) MUST BE marked with the keyword 'type key VALUE' (e.g., "type key SMRT_BLB").
  • schema name: A set of keywords indicating the name of the database table, used to store data for a Persistable C3.ai Type. Developer specify a schema name to customize database table names.

To learn more about C3.ai Type Definitions, please see the C3.ai resources here:

C3.ai Types 

There are many categories of C3.ai Types. 

We will cover the most commonly used categories in this tutorial:

  • Primitive Types 
  • Persistable Types 
  • Generic (Parametric) Types 
  • Abstract Types 

Primitive Types

Like many programming languages, the C3 AI Suite has primitives. Primitives define the units (or data types) of fields on a C3.ai Type (e.g., "int", "double", "float"). The C3 AI Suite includes a number of primitive, listed below for reference. Adding new primitives will require support from the C3.ai engineering team. However, most DTI researchers should be able to progress their research projects with the C3 AI Suite's existing primitives. 

C3 AI Suite Primitives

  • binary
  • boolean
  • byte
  • char
  • datetime
  • decimal
  • double
  • float
  • int
  • json
  • long int
  • string

To learn more about primitives, see the C3.ai Resources here:

Persistable Types

The most common C3.ai Type you will define is a 'Persistable' type. Persistable types store data in a C3 AI Suite database. By default, all Persistable types are stored in Postgres. However, Persistable types can also be stored in file systems (e.g., Azure Blob) or key-value stores (e.g., Cassandra), by adding an annotation to your .c3typ file (as will be discussed below). 

When defining a Persistable type, you MUST add the following two key terms to your .c3typ file

  1. entity: The keyword 'entity' keyword tells the C3 AI Suite this is a 'Persistable' type
  2. schema name: The schema names is the name of the database table, where the C3.a Type's data are stored. When defining a new Persistable type, you need to add the keywords 'schema name' followed by your chosen table name, in your .c3type file. (e.g., in the C3.ai Type LightBulb, we have 'schema name "LGHT_BLB"', where "LGHT_BLB" is our chosen table name). Please note, extended C3.ai DO NOT NEED a scheme name.

To learn more about 'Persistable' Types, please see the C3.ai Resources below: 

Generic (Parametric) Types

Like a Java or C++ Class, C3.ai Types can be parameterized (or genericized). In fact, the C3 AI Suite uses the exact same syntax as Java and C++ to define a type's parameters (i.e., angle brackets '<>' ). When defining a Generic (Parametric) type, your Type name will be followed by angle brackets and a comma-separated list of parameters (usually other C3.ai Types).  For example:

type TheType<V,U> {
	fieldA: V
	fieldB: [U]
}

Then, when using your Parametric Type in other places, you must specify the arguments for each parameter in the angle brackets. As example, you may define a new C3.ai Type with the following field:

type NewType {
	newField: TheType<TypeA, TypeB>
}

If your C3.ai types will be heavily re-used by other C3.ai developers, you should consider using Parametric (Generic) types.

To learn more about Generic Types, please see the C3.ai Resources here:

Fields

Each field of a C3 Type is defined by first giving the field name followed by a colon ':' followed by a Type.

The name must follow a set of conventions: – Name conventions here –

Depending on the Type of the field, the field is grouped into some broad categories.

Primitive Fields

Primitive Fields use types which are PrimitiveTypes. These are simple data types usually directly supported by database technologies such as SQL. These fields are stored directly within the main type's table.

Reference Fields

Reference Fields use another defined C3 Type. This reference is implemented in the main Type's table as a foreign key into the referenced Type's table, (The 'id' field!).

Collection Fields

Collection Fields are groups of Types. There are four broad categories of collections which can be used.

  • array: denoted by `[MyType]`
  • map: denoted by `map<int, MyType>` (the left argument is the key which can be any primitive type, while the right can be any Type.)
  • set: denoted by `set<MyType>`
  • stream: denoted by `stream<MyType>`. (This is a read-once type of collection).

When we define a field which is a collection of MyType, we can also specify which MyTypes get added by using Foreign Key Collection Notation. If for example, we're trying to define the new type TheType, and we want a collection to have type [MyType], but we don't want all MyTypes to be in this collection, we can use the notation `(thetype, id)` which specifies that C3 Should only use MyTypes whose 'thetype' property matches TheType's id property. Please see the section 'Foreign Key Collection Notation and Schema Names' in the following C3.ai documentation where this concept is explained better: https://developer.c3.ai/docs/7.12.17/topic/mda-fields

Calculated Fields

These fields are derived from other fields. Calculated fields are specified as follows

fieldName: FieldType [stored] calc "field_formula"

So, like the other types, we first specify a type for the field, then we must use the keyword 'calc' followed by an expression formula. (e.g, in the SmartBulb example above, this is 'fixtureHistory[0].(end == null).to').

You may also specify the keyword 'stored'. With the stored keyword, the value of the field is stored in the Type's Table, while without it is calculated at runtime.

C3.ai Developer Resources on Fields:

Methods

Each method of a C3 Type is defined by first giving the method name followed by a colon ':' followed by a function signature, (e.g., function() or function(wattage: !decimal, bulbType: !string)), followed by a colon ':', followed by a return Type, along with a language and environment specification.

Function Signatures

Instead of a Type name following the name as with fields, methods have a function signature. the syntax looks like:

function(parName1: [!] parType1, parName2: [!] parType2):

we see 'function' followed by a comma separated list of argument definitions. An argument definition consists of a parameter name followed by a colon ':', followed by a Type name. Optionally, you can also include an '!' before the parameter type to indicate that that argument is required.

Return Types

After the function signature we have a return type. This must be a C3 type. The function must return an object of the appropriate type in the .

Language/Environment Specification

Finally, we end with a language and environment specification.

Language Specifications

  • py: Use the 'py' keyword to indicate this is a Python function.
  • js: Use the 'js' keyword to indicate this is a JavaScript function.

Currently, these are the only two languages which the C3 AI Suite supports for native method definitions

Environment Specification

Then, this is followed by an environment specification. for JavaScript the only two environments are:

  • server: This JavaScript function is executed within the C3 AI Suite by a worker node.
  • client: This JavaScript function is executed at the client browser.

For Python, we need to specify a valid 'ActionRuntime' for the python function to be executed in. (ActionRuntimes are defined within the C3 AI Suite, and new ones can be defined as part of a C3 Package. They are essentially conda environments.) For example, 'server' will use the 'py-server' ActionRuntime.

Function Definitions

Finally, the function definition goes in a different file from the Type definition .c3typ file. It should have the same base name as the .c3typ definition file, but have an extension appropriate for the language of implementation (e.g., 'py' for Python, 'js' for JavaScript). Within, the file must contain functions whose name matches the method name from the Type definition exactly. For example, if we have a Type defined like so:

entity type NewType {
	field: double
	funcA: function(num: !int) : float js server
    funcB: function(num: !int, name: string) : double py server
}

This type will be in a file named 'NewType.c3typ',  There will also have to be two other files, 'NewType.js' and 'NewType.py'. Here's a peek inside:

'NewType.js'

function funcA(num) {
    ...
}

'NewType.py'

function funcB(num, name='default') {
    ...
}


C3.ai Developer Resources on Methods:

Inheritance

Like many modern languages, Types can also inherit fields and methods from other types in an object oriented fashion. The keywords which signal inheritance are `mixes` and `extends`. These follow the name of the new type.

Where C3 Type inheritance differs from other languages, is that these two keywords signal two different types of inheritance.

Mix-ins

This most basic type of inheritance is signaled by the 'mixes' keyword. When a type 'mixes' another type, it inherits all of the fields and methods of that type. The new type gets its own schema and table, and the user can go on to use it as they normally would.

type ParentType {
    fieldA: int
    funcA: function(): ReturnType
}

type ChildType mixes ParentType {
    fieldB: double
    funcB: function(): ReturnType
}

In this example, ChildType also includes the field and method defined in the parent type.

Persistable Inheritance

This second type of inheritance is signaled by the 'extends' keyword. When a type 'extends' another type, it inherits its fields and methods, but also resides in the same schema and table as the original type. In fact, a type can only extend a type which has been marked as `extendable`.

extendable entity type ParentType schema name "PRT_TYP" {
    fieldA: int
    funcA: function(): ReturnType
}

entity type ChildType extends ParentType type key "CHLD" {
    fieldB: double
    funcB: function(): ReturnType
}

In this example, ParentType is an extendable Persistable type stored in schema 'PRT_TYP'. ChildType extends ParentType and uses type key 'CHLD'.

C3.ai developer resources on Inheritance:

Annotations

Annotations can appear before Type definitions, field declarations, and method declarations. They do a variety of things such as configure the database technology used to store that Type, mark what type of ActionRuntime should be used to run a method, Mark a field or method as deprecated or beta, Specify a timeseries treatment, and specify parameters of C3 Analytics (discussion to come soon). These are only a selection of what annotations can do.

Here, we show a couple of examples:

  /**
   * This bulb's historical predictions.
   */
  @db(order='descending(timestamp)')
  bulbPredictions: [SmartBulbPrediction](smartBulb)

The bulbPredictions field is annotated that the database should store the bulbPredictions in descending order by timestamp.

@ts(treatment='avg')
lumens: double

The lumens field of a type be default is now going to use the 'AVG' treatment.

C3.ai developer resources on Annotations:

Final Keyword

Types, methods, and fields can be made 'final' using the 'final' keyword. This prevents fields and methods from being overridden by any type which mixes them in. If a type is made final, all fields and methods of that type are made final.

type FinalType {
    final method: function(): string
}

C3.ai developer resources on the final keyword

Abstract Types

The C3 AI Suite also supports abstract types. If you use the keyword 'abstract' before the type definition, this will indicate an abstract type. You cannot instantiate an abstract type, and must mix it into another concrete type. This provides a great way to standardize interfaces. Here's an example:

abstract type Interace {
	field1: int
    method1: function(double): int
}

C3.ai developer resources on Abstract types:

Examples

Look through the lightbulbAD package and find .c3typ files. Look at these to see the range of possibilities

  • No labels