2018年2月5日 星期一

LUIS訓練筆記

markdown

之前訓練過的筆記,只記在trello,也順便貼過來

筆記

Utterances 話語

話語就是你應用程式期望會收到並想要解釋的使用者查詢語句
增加話語來訓練LUIS讓他懂類似的語句

Intent 意圖

意圖就是動機或者有預期的行動
有個盲點就是有話語通常有意圖
但不一定能從話語中找出實體
這是過去我一直卡住的地方
所以訓練才會卡卡的
應該要把意圖與實體分開看

Entities 實體

實體就是在你應用程式領域中關鍵的資料
例如 關鍵字 日期 班機號碼 天數
例如一個請假功能 輸入
我想在下周三請兩小時的假
透過意圖訓練可以辨識出請假的意圖
但在語句中抓出的實體應該為下周三兩小時

2018年2月2日 星期五

SQL Server 民國日期時間 數字轉datetime | SQL Server Taiwan Calander date and time number to datetime

markdown ``` --原始欄位: --民國年數字date_number,時間數字time_number --(ex:971201,6) --將民國數字轉西元字串 select cast(970101+19110000 as varchar(8)) --output:20080101 --將西元字串轉YYYY-MM-DD字串 select SUBSTRING(cast(970101+19110000 as varchar(8)),1,4)+'-' +SUBSTRING(cast(970101+19110000 as varchar(8)),5,2)+'-' +SUBSTRING(cast(970101+19110000 as varchar(8)),7,2) --output:2008-01-01 --將時分數字PAD成時分秒,在零點時可能只有個位數 如 00:06 =6 select REPLACE(STR(cast(6 as varchar(6)), 6), SPACE(1), '0') --output:000006 --將補齊的時分秒轉為HH:MM:SS字串 select substring(REPLACE(STR(cast(6 as varchar(6)), 6), SPACE(1), '0') ,1,2)+':' +substring(REPLACE(STR(cast(6 as varchar(6)), 6), SPACE(1), '0') ,3,2)+':' +substring(REPLACE(STR(cast(6 as varchar(6)), 6), SPACE(1), '0') ,5,2) --output:00:00:06 --將時間字串與時分秒字串轉為DATETIME select convert(datetime, SUBSTRING(cast(970101+19110000 as varchar(8)),1,4)+'-' +SUBSTRING(cast(970101+19110000 as varchar(8)),5,2)+'-' +SUBSTRING(cast(970101+19110000 as varchar(8)),7,2) +' ' +substring(REPLACE(STR(cast(6 as varchar(6)), 6), SPACE(1), '0') ,1,2)+':' +substring(REPLACE(STR(cast(6 as varchar(6)), 6), SPACE(1), '0') ,3,2)+':' +substring(REPLACE(STR(cast(6 as varchar(6)), 6), SPACE(1), '0') ,5,2) ,121) --output:2008-01-01 00:00:06.000 ```

2018年1月25日 星期四

Kendo UI Grid Filter DatePicker 過濾時分秒 | Kendo UI Grid Filter DatePicker truncate time

markdown

#背景
當grid使用server端做分頁排序篩選時,查詢的邏輯就是靠`ToDataSourceResult`來控制,
所以改不了內容在幹嘛,
當前端Grid需要篩選某日期欄位,該欄位又有時分秒,且使用者不想設定秒數時,
預設的時間篩選欄位只會傳入年月日到後端,時分秒為零,
但這樣在做小於等於或者等於的查詢時就會遇到時分秒不相同而查不到資料的問題,
如果是自己做api,這種小地方可以調整用`System.Data.Entity.DbFunctions.TruncateTime()`來過濾時間,但用Telerik的類別處理時就不行了,

後來查到官方的解法如連結
https://docs.telerik.com/kendo-ui/controls/data-management/grid/how-to/filtering/filter-by-date
大致上就是說要在filter menu初始化時,複寫套用的按鈕的事件,
例如原本設"等於"條件時,只會傳入一個邏輯就是相等一個日期時間值,
我們要調整為兩個邏輯,
用大於等於當天的零點零分零秒與 小於等於當天的23點59分59秒兩個條件傳入後端,
這樣就可以避免對不上時間的問題了

```
filterMenuInit: function (e) { //當日期條件設定為小於/等於時 只會傳入年月日 不會傳入時分秒 //但這樣會撈不到當天0點之後的資料 所以要幫他加到23:59:59來方便處理 //(因為沒辦法調整語法為trunc time) var menu = $(e.container); if (e.field === "ColumnDateTime") { var firstDropDown = $('[data-bind="value: filters[0].operator"]').data('kendoDropDownList'); menu.find('button[type="submit"]').click(function (ev) { if (firstDropDown.value() === 'eq') { ev.preventDefault(); var selectedDate = menu.find('[data-role="datepicker"]').first().data('kendoDatePicker').value(); if (!selectedDate) { $(ev.target).closest('[data-role="popup"]').data('kendoPopup').close(); return; } var startOfFilterDate = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate()); var endOfFilterDate = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), 23, 59, 59); var filter = { logic: "and", filters: [ { field: "ColumnDateTime", operator: "gte", value: startOfFilterDate }, { field: "ColumnDateTime", operator: "lte", value: endOfFilterDate } ] }; e.sender.dataSource.filter(filter); $(ev.target).closest('[data-role="popup"]').data('kendoPopup').close(); return; } else if (firstDropDown.value() === 'le') { ev.preventDefault(); var selectedDate = menu.find('[data-role="datepicker"]').first().data('kendoDatePicker').value(); if (!selectedDate) { $(ev.target).closest('[data-role="popup"]').data('kendoPopup').close(); return; } var endOfFilterDate = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), 23, 59, 59); var filter = { logic: "and", filters: [ { field: "ColumnDateTime", operator: "lte", value: endOfFilterDate } ] }; e.sender.dataSource.filter(filter); $(ev.target).closest('[data-role="popup"]').data('kendoPopup').close(); return; } }); } }, ```

2017年12月20日 星期三

Kendo ui Combobox 在 blur 時觸發change 事件 | Kendo ui Combobox blur trigger change event

同事遇到的BUG,
在Kendo ui Combobox選完值,blur時會觸發change事件,
經過實驗(還有我一直google)同時發現,
如果在text field使用number類型欄位,就會這樣,
而且官方好像沒有要改的意思

ref: https://github.com/telerik/kendo-angular/issues/1137

2017年12月19日 星期二

Telerik Report Nullable Date Parameter

markdown

#背景
有個報表,搜尋條件是兩種日期選一種,日期欄位就是區間開始與區間結束,
在報表檔案設定四個日期變數:A起始、A結束、B起始、B結束,都可以為空值,
然後還要做民國年轉換,
結果在表頭顯示輸入變數的地方就調了很久,終於成功...

#解法

原來要做以下處理:

1. 判斷變數是否為null
```
Parameters.P_InitDateStart is null ```

2. 調整UTC時區問題,沒有自動+8,要手動+8
```
AddHours(Parameters.P_InitDateStart,8) ```

3. 加上小時前,還要做IsNull判斷讓他一定要回傳一個非空的日期值
```
AddHours(IsNull(Parameters.P_InitDateStart, Date(1,1,1)),8) ```

4. 把年份減去1911
```
(AddHours(IsNull(Parameters.P_InitDateStart, Date(1,1,1)),8).Year-1911) ```

5.最後拼起來這些處理就變成這樣:
```
= IIF(Parameters.P_InitDateStart is null, (AddHours(IsNull(Parameters.P_ArriveDateStart, Date(1,1,1)),8).Year-1911)+"/" +AddHours(IsNull(Parameters.P_ArriveDateStart, Date(1,1,1)),8).Month+"/" +AddHours(IsNull(Parameters.P_ArriveDateStart, Date(1,1,1)),8).Day, (AddHours(IsNull(Parameters.P_InitDateStart, Date(1,1,1)),8).Year-1911)+"/" +AddHours(IsNull(Parameters.P_InitDateStart, Date(1,1,1)),8).Month+"/" +AddHours(IsNull(Parameters.P_InitDateStart, Date(1,1,1)),8).Day) ```

 暈...

2017年12月8日 星期五

Kendo UI Date Picker 民國年格式 | Kendo UI Date Picker Taiwan calander format

markdown

#背景
一個Grid,在編輯時用popup,內容是template,
有個日期欄位,
使用者想要輸入1060101或者10601011735這種日期或日期時間格式
不想要選取也不想要打「-」、空白或「:」

研究了一下也提問在官方討論區了,官方是說不支援民國年這種格式,
實際上你輸入1900年之前的日期,也會無法set,
所以也不能用民國106年直接套上DatePicker/DateTimePicker,而且萬年曆也會不對,

這裡就用textbox自幹一個字串轉日期的方法
(如果想要讓後端收到的值仍然有「-」、空白或「:」,是可以搭配mask)

首先在原本的template的時間輸入框前面加入一個input textbox
```
```
然後準備好字串轉日期、日期轉字串的處理,
這裡列的方法是我很直觀的去寫,沒有做什麼效能分析

```
//傳入值與格式,分析與轉換成JS日期物件 function parseTWDate(val, format) { var result = { result: true, msg: "", DateObj: null }; if (typeof val === "undefined") { result.result = false; return result; } //只接受數字與字串型態 if (typeof val !== "number" && typeof val !== "string") { result.result = false; return result; } //檢查格式字串 if ( format !== "yyyMMdd" && format !== "yyyMMddHHmm" && format !== "yyyMMddHHmmss" ) { result.result = false; result.msg = "不支援的日期格式設定"; return result; } //只接受數字不接受符號 if (/^\d+$/.test(val) === false) { result.result = false; return result; } var str = ""; //數字型態轉文字 if (typeof val === "number") { str = val.toString(); } else { str = val; } //從後往前兩位兩位取出字串,轉數字後做驗證 var sec = 0; var min = 0; var hr = 0; var day = 0; var mon = 0; var year = 0; var movingCount = 0; if (format === "yyyMMddHHmmss") { if (str.length < 11) { result.result = false; return result; } sec = parseInt( str.substring(str.length - movingCount - 2, str.length - movingCount), 10 ); if (sec === NaN || sec > 59 || sec < 0) { result.result = false; return result; } movingCount += 2; } if (format === "yyyMMddHHmm" || format === "yyyMMddHHmmss") { if (str.length < 9) { result.result = false; return result; } min = parseInt( str.substring(str.length - movingCount - 2, str.length - movingCount), 10 ); if (min === NaN || min > 59 || min < 0) { result.result = false; return result; } movingCount += 2; hr = parseInt( str.substring(str.length - movingCount - 2, str.length - movingCount), 10 ); if (hr === NaN || hr > 23 || hr < 0) { result.result = false; return result; } movingCount += 2; } if (str.length < 5) { result.result = false; return result; } day = parseInt( str.substring(str.length - movingCount - 2, str.length - movingCount), 10 ); if (day === NaN || day > 31 || day < 1) { result.result = false; return result; } movingCount += 2; mon = parseInt( str.substring(str.length - movingCount - 2, str.length - movingCount), 10 ); if (mon === NaN || mon > 12 || mon < 1) { result.result = false; return result; } movingCount += 2; year = parseInt(str.substring(0, str.length - movingCount), 10); if (year === NaN || year < 1) { result.result = false; return result; } //JS的日期的月份是0 base result.DateObj = new Date(year + 1911, mon - 1, day, hr, min, sec); return result; } //將日期選擇轉成民國年 function getTWDate(val, format) { var rawYear = val.getFullYear(); var twYear = rawYear - 1911; var rawMonth = val.getMonth() + 1; var twMonth = rawMonth.toString().padStart(2, "0"); var rawDay = val.getDate(); var twDay = rawDay.toString().padStart(2, "0"); var strTWDate = twYear + twMonth + twDay; if (format === "yyyMMddHHmm" || format === "yyyMMddHHmmss") { var hr = val .getHours() .toString() .padStart(2, "0"); var min = val .getMinutes() .toString() .padStart(2, "0"); strTWDate = strTWDate + hr + min; if (format === "yyyMMddHHmmss") { var sec = val .getSeconds() .toString() .padStart(2, "0"); strTWDate = strTWDate + sec; } } return strTWDate; } ```
然後在原本的datasource的schema中加入一個自訂的民國年欄位,類型是字串
這個欄位不會回傳值給API,只是用來data bind,
裡面做自定義驗證方法,在民國年字串輸入框跳開時,去跑日期解析,
成功就把日期物件帶回model,失敗就給予錯誤訊息並回傳false;
原本的欄位也給他一個驗證是如果跑到了驗證就檢查他的值是否可以解析成日期,
這裡弄了很久是不知如何避免偶爾會出現原欄位跳出必填的問題,
還有編輯時無法透過model.set讓他自己data bind去改變前端DOM的值,
set下去model的值竟然不會改,只能手動去塞值&改DOM,
改值的時候也要先改DOM在做SET,不然也會有怪現象

```
var dataSource = { schema: { model: { fields: { myDate: { type: "date", validation: { required: true, dateFormat: function(input, option) { if (input.is("[name='myDate']")) { if (Date.parse(input.val()) !== NaN) { $("#myDateTW").removeAttr("data-TWdate-msg"); $("#myDate").removeAttr("data-required-msg"); return true; } else { $("#myDateTW").attr( "data-TWdate-msg", "格式民國年yyyMMdd" ); return false; } } else { return true; } } } }, myDateTW: { type: "string", validation: { required: true, TWdate: function(input, option) { if (input.is("[name='myDateTW']")) { var val = input.val(); var format = "yyyMMdd"; var result = parseTWDate(val, format); if (result.result) { input.removeAttr("data-TWdate-msg"); //帶回model var source = $("#myDateTW")[0].kendoBindingTarget .source; $("#myDate").val(result.DateObj); source.set("myDate", result.DateObj); return true; } else { input.attr("data-TWdate-msg", "格式民國年" + format); //帶回model var source = $("#myDateTW")[0].kendoBindingTarget .source; $("#myDate").val(val); source.set("myDate", val); return false; } } else { return true; } } }, } } } }, }; }; ```

再來是grid新增或編輯時需要把model的date欄位清空與轉換
```
edit: function(e) { var arg = e; if (e.model.isNew()) { //日期型態先清空 e.model.set("myDate", null); } else { //民國年要先轉好 var myDate = e.model.get("ㄗㄩDate"); var strMyDateTW = getTWDate(myDate, "yyyMMdd"); e.model.myDateTW = strMyDateTW; e.model.set("myDateTW", strMyDateTW); $("#myDateTW").val(strMyDateTW); } } ```

這樣就可以輸入民國年日期轉成西元年日期,編輯時也可以把西元年日期轉回民國年日期了

2017年12月4日 星期一

Kendo UI Grid + datasource 錯誤處理 | Kendo UI Grid datasource error handle

markdown

(我的Kendo版本是2017.3.913)

最常用的CRUD應用元件,KendoUI Grid + Datasource transport,
可以簡單串起Restful API,
但錯誤處理上有一些地方要注意

使用transport時API如果不拋出500或者404之類的錯誤,
而是用處理完成,但處理結果有誤,錯誤訊息放在一個字串屬性裡面的這種模式,
datasource無法去抓出那個物件秀出來,
這時就要用因為有錯誤所以要拋exception的概念,
讓API拋出HttpStatusCode.InternalServerError,
這樣前端ajax呼叫時就會抓到錯誤,
然後丟給datasource裡的error屬性事件處理,
就可以用e.xhr.responseJSON.Message抓出錯誤內容,
但也要加上undefined偵測,不是只有API邏輯錯的時候才會丟錯
所以error物件裡面也不是常常都有xhr


如果是自己寫的介面,例如另外寫kendo window,
並且自訂按鈕送出ajax,你就可以控制ajax的邏輯,
要用「有錯就是要拋錯」或者「你成功執行那就給我200,商業邏輯錯誤再另外包」
哪一種抓錯方式都可以處理了。

另外如果新增刪除修改的API用void類型,datasource的transport會拋一個JSON parsererror的錯,但若是給他回傳值,則不會有這個錯
我用的回傳物件格式:
```
public class OperationOutModel { ///執行結果 public bool result { get; set; } ///錯誤訊息 public string msg { get; set; } } ```