// Copyright (c) 2001, 2004, Oracle Corporation.  All rights reserved.  
// File: CostDemo.java

/**
 * This sample program describes how to use the ODM Java API to provide a cost
 * matrix to build a classification model using the NaiveBayes(NB) algorithm.
 * This program uses the "MINING_DATA_BUILD_V" dataset for building a
 * model to predict which customer should receive an "AFFINITY_CARD".
 * It then applies the built model on "MINING_DATA_APPLY_V" dataset to predict 
 * which customers must receive "AFFINITY_CARD".  We will compare apply results
 * on a built model with the cost matrix and a built model without the cost 
 * matrix.
 * 
 * NOTE:
 *   Before executing this program you need to provide DB URL, username 
 *   and password in login method and a valid data schema name
 *   by changing the DATA_SCHEMA_NAME constant value in the program.
 */

// Generic Java Imports
import java.io.*;
import java.util.*;
import java.sql.*;

// ODM Imports
import oracle.dmt.odm.*;
import oracle.dmt.odm.data.*;
import oracle.dmt.odm.settings.function.*;
import oracle.dmt.odm.settings.algorithm.*;
import oracle.dmt.odm.task.*;
import oracle.dmt.odm.model.*;
import oracle.dmt.odm.result.*;

public class CostDemo extends Object {

  private static DataMiningServer m_dms = null;
  private static oracle.dmt.odm.Connection m_dmsConn = null;
  //provide schema name that has the data tables/views
  private static final String DATA_SCHEMA_NAME = "schema name";

  public static void main( String args[] ) { 
    try {      
      //1. Login to the Data Mining Server
          //Create a DMS object
          m_dms = new DataMiningServer(
            "put DB URL here", //JDBC URL jdbc:oracle:thin:@<Host name>:<Port>:<SID>
            "user name", //User Name
            "password" //Password 
          );
          //Login to the DMS and create a DMS Connection
          m_dmsConn = m_dms.login();

        //Remove the objects with cost matrix to avoid name conflicts
        cleanupWithCostMatrix();
      //2. Build a model with cost matrix
      buildModelWithCostMatrix();
      //3. Apply the model with cost matrix
      applyModelWithCostMatrix();
      
          //Remove the objects without cost matrix to avoid name conflicts
          cleanup();
      //4. Build a model without cost matrix
      buildModel();
      //5. Apply the model
      applyModel();
      
    } catch(Exception anyExp) {
      anyExp.printStackTrace(System.out);
    } finally {
      try {
      //6. Logout from the Data Mining Server
      m_dms.logout(m_dmsConn);
      } catch(Exception anyExp1) { }//Ignore
    }
  }

  /**
   * To build a mining model with cost matrix, the build dataset location 
   * details and the mining function settings are required. 
   * In this example, we will build a classification model with cost matrix 
   * using the build dataset "MINING_DATA_BUILD_V" in the user schema, with the 
   * NaiveBayes algorithm. This model can be used to predict which customer 
   * should receive an "AFFINITY_CARD". After completing the build task, the 
   * model named "CostDemo_Model" will be created in the DMS.
   */
  public static void buildModelWithCostMatrix() 
    throws ODMException, SQLException, IOException 
  {
    //1. Create PhysicalDataSpecification
        LocationAccessData lad = new LocationAccessData( 
                                       "MINING_DATA_BUILD_V", //Build Table Name
                                       DATA_SCHEMA_NAME //Schema Name
                                     );
        PhysicalDataSpecification pds = 
          new NonTransactionalDataSpecification( lad );
    //2. Create & Store Mining Function Settings
        NaiveBayesSettings nbAlgo = new NaiveBayesSettings(0.01f, 0.01f);
        ClassificationFunctionSettings mfs =
          ClassificationFunctionSettings.create(
                   m_dmsConn,       //DMS Connection
                   nbAlgo,          //NB algorithm settings
                   pds,             //Build data specification
                   "AFFINITY_CARD", //Target column
                   AttributeType.categorical, //Default attribute type
                   DataPreparationStatus.unprepared //Default preparation status
                   );
        //Set inactive attributes by changing the default attribute usage
        mfs.adjustAttributeUsage(new String[]{"CUST_ID"},AttributeUsage.inactive);

        // Set Cost Matrix
        // Cost Matrix is used in a classification problem. It enables the user 
        // to let algorithm knows what relative values of various classes are 
        // to the user.  For example, suppose a problem is to predict whether a 
        // user will respond to a promotional mailing, "AFFINITY_CARD".  The 
        // target has two classes: YES (1) and NO (0). Suppose a positive (YES)
        // response to the promotion generates $2 and the cost of the 
        // promotion is $1.  Then the cost of misclassify a positive responder 
        // is $2. The cost of misclassify a non-responder is $1.  
        //
        // This is a complex object. To create cost matrix you need to specify 
        // the row and column category objects. After that you need to specify 
        // each matrix cell's value.
        //
        // For example to represent the above cost matrix
        //
        //        predicted
        // 
        //      | 0    1
        //     -|---------
        //  a  0| 0    1
        //  c   |
        //  t  1| 2    0
        //  u
        //  a
        //  l
        //
        // Some algorithms directly optimize for the cost matrix, modifying the 
        // model structure so as to directly produce minimal cost solutions. 
        // Other algorithms, like NaiveBayes(NB), that will predict 
        // probabilities, use the Cost Matrix during scoring to propose the 
        // least cost solution.
        //
        // For example, suppose a model with the above cost matrix predicts a 
        // probability of YES of 0.05. Then the cost of predicting YES (1) is 
        // 0.95 * $1 = $0.95 Whereas the cost of predicting NO (0) is 
        // 0.05 * $2 = $0.1.  So the recommendation would be not to send the 
        // promotion since the cost of sending out promotion is must higher than 
        // the cost of not sending out promotion.  The default value of cost 
        // matrix will be a cost of missing an expected result.

        // Define a list of categories
        Category negativeCat = new Category("negativeResponse", "0", 
                                            DataType.intType);
        Category positiveCat = new Category("positiveResponse", "1", 
                                            DataType.intType);      

        // Define a Cost Matrix
        // AddEntry( Actual Category, Predicted Category, Cost Value)
        CostMatrix costMatrix = new CostMatrix();
        // Row 1
        costMatrix.addEntry(negativeCat, negativeCat, new Integer("0"));
        costMatrix.addEntry(negativeCat, positiveCat, new Integer("1"));
        // Row 2
        costMatrix.addEntry(positiveCat, negativeCat, new Integer("2"));
        costMatrix.addEntry(positiveCat, positiveCat, new Integer("0"));
        // Add Cost Matrix
        if (costMatrix != null) {
          mfs.setCostMatrix(costMatrix);
        }

        mfs.store( m_dmsConn, "CostDemo_MFS");
    //3. Create, store & execute Build Task
        MiningBuildTask buildTask = new MiningBuildTask(
                     pds, //Build data specification
                     "CostDemo_MFS", //Mining function settings name
                     "CostDemo_Model" //Mining model name
                     );
        executeTask(buildTask, "CostDemo_BuildTask" );
  }

  /**
   * To build a mining model without cost matrix, the build dataset location 
   * details and the mining function settings are required. 
   * In this example, we will build a classification model using the 
   * build dataset "MINING_DATA_BUILD_V" in the user schema, with the NaiveBayes 
   * algorithm. This model can be used to predict which customer should 
   * receive an "AFFINITY_CARD". After completing the build task, the model 
   * named "CostDemo_Model" will be created in the DMS.
   */
  public static void buildModel() throws ODMException, SQLException, IOException 
  {
    //1. Create PhysicalDataSpecification
        LocationAccessData lad = new LocationAccessData( 
                                       "MINING_DATA_BUILD_V", //Build Table Name
                                       DATA_SCHEMA_NAME //Schema Name
                                     );
        PhysicalDataSpecification pds = 
          new NonTransactionalDataSpecification( lad );
    //2. Create & Store Mining Function Settings
        NaiveBayesSettings nbAlgo = new NaiveBayesSettings(0.01f, 0.01f);
        ClassificationFunctionSettings mfs =
          ClassificationFunctionSettings.create(
                   m_dmsConn,       //DMS Connection
                   nbAlgo,          //NB algorithm settings
                   pds,             //Build data specification
                   "AFFINITY_CARD", //Target column
                   AttributeType.categorical, //Default attribute type
                   DataPreparationStatus.unprepared //Default preparation status
                   );
        //Set inactive attributes by changing the default attribute usage
        mfs.adjustAttributeUsage( new String[]{"CUST_ID"},
                                  AttributeUsage.inactive);
        mfs.store( m_dmsConn, "NoCostDemo_MFS");
    //3. Create, store & execute Build Task
        MiningBuildTask buildTask = new MiningBuildTask(
                     pds, //Build data specification
                     "NoCostDemo_MFS", //Mining function settings name
                     "NoCostDemo_Model" //Mining model name
                     );
        executeTask(buildTask, "NoCostDemo_BuildTask" );
  }
  
  /**
   * This method stores the given task with the specified name in the DMS 
   * and submits the task for asynchronous execution in the DMS. After 
   * completing the task successfully it returns true. If there is a task 
   * failure, then it prints error description and returns false.
   * 
   * @param taskObj task object
   * @param taskName name of the task
   * 
   * @return boolean returns true when the task is successful
   */
  public static boolean executeTask(MiningTask taskObj, String taskName)
    throws ODMException, SQLException {
    boolean isTaskSuccess = false;
    taskObj.store(m_dmsConn, taskName); //Store the task
    taskObj.execute(m_dmsConn); //Submit the task for asynchronous execution
    System.out.print(taskName + " is started, please wait. ");
    //Wait for completion of the task
    MiningTaskStatus taskStatus = taskObj.waitForCompletion(m_dmsConn);
    //Check the status of the task after completion
    isTaskSuccess = taskStatus.getTaskState().equals(MiningTaskState.success);
    if( isTaskSuccess ) {
      //Task completed successfully
      System.out.println("It is successful.");
    } else {//Task failed
      throw new ODMException("It is failed.\nFailure Description: " + 
        taskStatus.getStateDescription() );
    }
    return isTaskSuccess;
  }

  /**
   * To apply a mining model without cost matrix to a data, apply dataset 
   * location details, input model and the output table location details are 
   * required. 
   * In this example, we apply the CostDemo_Model to the "MINING_DATA_APPLY_V"  
   * dataset to predict which customers receive an "AFFINITY_CARD".
   * After completing the apply task, CostDemo_ApplyResults object will be  
   * created in the DMS and an apply output table will be created at the  
   * specfied location.
   */
  public static void applyModel() throws ODMException, SQLException, IOException 
  {
    //1. Create PhysicalDataSpecification
        LocationAccessData lad = new LocationAccessData( 
                                 "MINING_DATA_APPLY_V", //Apply table/view Name
                                 DATA_SCHEMA_NAME //Schema Name
                               );
        PhysicalDataSpecification pds = 
          new NonTransactionalDataSpecification( lad );
    //2. Create MiningApplyOutput & apply output table/view details
        // Create MiningApplyOutput object
        MiningApplyOutput mao = MiningApplyOutput.createDefault();
        // Add all the source attributes to be returned with the scored result.
        // In this example, attribute "CUST_ID" from the source table will be 
        // returned with the result aliased as "CUST_ID"
        MiningAttribute sourceAttribute =
                  new MiningAttribute("CUST_ID", DataType.intType,
                                      AttributeType.notApplicable);
        Attribute destinationAttribute = new Attribute("CUST_ID", DataType.intType);

        ApplySourceAttributeItem m_ApplySourceAttributeItem =
           new ApplySourceAttributeItem(sourceAttribute,destinationAttribute);
        // Add the ApplySourceAttributeItem object to the MiningApplyOutput
        mao.addItem(m_ApplySourceAttributeItem);
        //Output table location details
        LocationAccessData outputTable =
                 new LocationAccessData("NoCostDemo_Apply_Output_Table", DATA_SCHEMA_NAME);      
    //3. Create, store & execute apply Task
        MiningApplyTask applyTask = new MiningApplyTask(
                     pds, //test data specification
                     "NoCostDemo_Model", //Input model name
                     mao, //MiningApplyOutput object
                     outputTable, //Apply output table location details
                     "NoCostDemo_ApplyResults" //Apply results name
                     );
        //Store & execute the task
        boolean isTaskSuccess = executeTask(applyTask, "NoCostDemo_ApplyTask"); 
        if( isTaskSuccess ) {
          //Restore & display apply results
          MiningApplyResult applyResult = 
            MiningApplyResult.restore(m_dmsConn, "NoCostDemo_ApplyResults");
          System.out.println(applyResult.toString());
          executeQuery(m_dmsConn.getDBConnection(),
            "SELECT CUST_ID, MAO_PREDICTION, MAO_PROBABILITY " +
            "FROM NoCostDemo_Apply_Output_Table " + 
            "WHERE CUST_ID < 100010 ORDER BY CUST_ID",
            "CUST_ID | MAO_PREDICTION | MAO_PROBABILITY");
        }
  }

  /**
   * To apply a mining model with cost matrix to a data, apply dataset location 
   * details, input model and the output table location details are required. 
   * In this example, we apply the CostDemo_Model to the "MINING_DATA_APPLY_V"  
   * dataset to predict which customers receive an "AFFINITY_CARD".
   * After completing the apply task, CostDemo_ApplyResults object will be  
   * created in the DMS and an apply output table will be created at the  
   * specfied location.
   */
  public static void applyModelWithCostMatrix() 
    throws ODMException, SQLException, IOException 
  {
    //1. Create PhysicalDataSpecification
        LocationAccessData lad = new LocationAccessData( 
                                 "MINING_DATA_APPLY_V", //Apply table/view Name
                                 DATA_SCHEMA_NAME //Schema Name
                               );
        PhysicalDataSpecification pds = 
          new NonTransactionalDataSpecification( lad );
    //2. Create MiningApplyOutput & apply output table/view details
        // Create MiningApplyOutput object
        MiningApplyOutput mao = 
                  MiningApplyOutput.createDefault("MAO_PREDICTION", "MAO_COST");
        // Add all the source attributes to be returned with the scored result.
        // In this example, attribute "CUST_ID" from the source table will be 
        // returned with the result aliased as "CUST_ID"
        MiningAttribute sourceAttribute =
                  new MiningAttribute("CUST_ID", DataType.intType,
                                      AttributeType.notApplicable);
        Attribute destinationAttribute = new Attribute("CUST_ID", 
                                                        DataType.intType);

        ApplySourceAttributeItem m_ApplySourceAttributeItem =
           new ApplySourceAttributeItem(sourceAttribute,destinationAttribute);
        // Add the ApplySourceAttributeItem object to the MiningApplyOutput
        mao.addItem(m_ApplySourceAttributeItem);
        //Output table location details
        LocationAccessData outputTable =
                 new LocationAccessData("CostDemo_Apply_Output_Table", 
                                        DATA_SCHEMA_NAME);      
    //3. Create, store & execute apply Task
        MiningApplyTask applyTask = new MiningApplyTask(
                     pds, //test data specification
                     "CostDemo_Model", //Input model name
                     mao, //MiningApplyOutput object
                     outputTable, //Apply output table location details
                     "CostDemo_ApplyResults" //Apply results name
                     );
        //Store & execute the task
        boolean isTaskSuccess = executeTask(applyTask, "CostDemo_ApplyTask"); 
        if( isTaskSuccess ) {
          //Restore & display apply results
          MiningApplyResult applyResult = 
            MiningApplyResult.restore(m_dmsConn, "CostDemo_ApplyResults");
          System.out.println(applyResult.toString());
          executeQuery(m_dmsConn.getDBConnection(),
            "SELECT CUST_ID, MAO_PREDICTION, MAO_COST " +
            "FROM CostDemo_Apply_Output_Table " +
            "WHERE CUST_ID < 100010 ORDER BY CUST_ID",
            "CUST_ID | MAO_PREDICTION | MAO_COST");
        }
  }

  /**
   * Cleanup the previously created objects with cost matrix if any. 
   */
  public static void cleanupWithCostMatrix() throws ODMException, SQLException
  {
    //Remove the MiningFunctionSettings
    try {
      ClassificationFunctionSettings.remove(m_dmsConn, "CostDemo_MFS");
    }catch(Exception anyExp1) { }//Ignore
    //Remove the MiningBuildTask
    try {
      MiningBuildTask.remove(m_dmsConn, "CostDemo_BuildTask");
    }catch(Exception anyExp2) { }//Ignore
    //Remove the MiningModel
    try {
      MiningModel.remove(m_dmsConn, "CostDemo_Model");;
    }catch(Exception anyExp3) { }//Ignore
    //Remove the MiningApplyTask
    try {
      MiningApplyTask.remove(m_dmsConn, "CostDemo_ApplyTask");
    }catch(Exception anyExp8) { }//Ignore
    //Remove the MiningApplyResult and apply output table
    try {
      MiningApplyResult.remove(m_dmsConn, "CostDemo_ApplyResults", true);
    }catch(Exception anyExp9) { }//Ignore    
  }
  
  /**
   * Cleanup the previously created objects without cost matrix if any. 
   */
  public static void cleanup() throws ODMException, SQLException
  {
    //Remove the MiningFunctionSettings
    try {
      ClassificationFunctionSettings.remove(m_dmsConn, "NoCostDemo_MFS");
    }catch(Exception anyExp1) { }//Ignore
    //Remove the MiningBuildTask
    try {
      MiningBuildTask.remove(m_dmsConn, "NoCostDemo_BuildTask");
    }catch(Exception anyExp2) { }//Ignore
    //Remove the MiningModel
    try {
      MiningModel.remove(m_dmsConn, "NoCostDemo_Model");
    }catch(Exception anyExp3) { }//Ignore
    //Remove the MiningApplyTask
    try {
      MiningApplyTask.remove(m_dmsConn, "NoCostDemo_ApplyTask");
    }catch(Exception anyExp8) { }//Ignore
    //Remove the MiningApplyResult and apply output table
    try {
      MiningApplyResult.remove(m_dmsConn, "NoCostDemo_ApplyResults", true);
    }catch(Exception anyExp9) { }//Ignore    
  }

  public static void executeQuery ( java.sql.Connection conn, String query, 
                                    String header) throws SQLException
  {
    Statement stmt = conn.createStatement ();
    ResultSet rset = stmt.executeQuery (query);
    try
    {
      // Iterate through the result and print the employee names
      System.out.println("Scoring result:");
      System.out.println(header);
      while (rset.next ())
        System.out.println (rset.getString (1) + " | " +
                            rset.getString (2) + " | " +
                            rset.getString (3));
    }
    finally
    {
      // Close the RseultSet
      rset.close();
      // Close the Statement
      stmt.close();
    }
  }
}
