Skip to content

Commit

Permalink
Title: Fix Intent Mapping and Field Validation in Number Series Gener…
Browse files Browse the repository at this point in the history
…ation

Summary: This commit resolves three bugs in the Number Series generation process. It improves intent mapping for the "Generate" command, corrects field checks for existing Number Series, and refines validation logic to allow optional fields to be blank. These fixes enhance the accuracy and reliability of Number Series generation.

Fixes #2106
  • Loading branch information
DmitryKatson committed Sep 27, 2024
1 parent 177669e commit 816520c
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------

namespace Microsoft.Foundation.NoSeries;

permissionset 330 "No. Series Copilot - Objects"
{
Access = Internal;
Assignable = false;
Permissions =
codeunit "No. Series Copilot Impl." = X,
codeunit "No. Series Text Match Impl." = X;
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ codeunit 324 "No. Series Copilot Impl."
Telemetry: Codeunit Telemetry;
ToolsSelectionPrompt: Text;
begin
if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotToolsSelectionPrompt', ToolsSelectionPrompt) then begin
if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotToolsSelectionPromptV2', ToolsSelectionPrompt) then begin // TODO: As the prompt should be different for v25.0 and v25.1, we need to add a new secret for v25.1 and update the code to get the correct prompt based on the version
Telemetry.LogMessage('0000NDY', TelemetryToolsSelectionPromptRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata);
Error(ToolLoadingErr);
end;
Expand Down Expand Up @@ -346,52 +346,80 @@ codeunit 324 "No. Series Copilot Impl."
for i := 0 to Json.GetCollectionCount() - 1 do begin
Json.GetObjectFromCollectionByIndex(i, NoSeriesObj);
Json.InitializeObject(NoSeriesObj);
CheckTextPropertyExistAndCheckIfNotEmpty('seriesCode', Json);
CheckMaximumLengthOfPropertyValue('seriesCode', Json, 20);
CheckTextPropertyExistAndCheckIfNotEmpty('description', Json);
CheckTextPropertyExistAndCheckIfNotEmpty('startingNo', Json);
CheckMaximumLengthOfPropertyValue('startingNo', Json, 20);
CheckTextPropertyExistAndCheckIfNotEmpty('endingNo', Json);
CheckMaximumLengthOfPropertyValue('endingNo', Json, 20);
CheckTextPropertyExistAndCheckIfNotEmpty('warningNo', Json);
CheckMaximumLengthOfPropertyValue('warningNo', Json, 20);
CheckIntegerPropertyExistAndCheckIfNotEmpty('incrementByNo', Json);
CheckIntegerPropertyExistAndCheckIfNotEmpty('tableId', Json);
CheckIntegerPropertyExistAndCheckIfNotEmpty('fieldId', Json);
CheckMandatoryProperties(Json);
end;
end;

local procedure CheckMandatoryProperties(var Json: Codeunit Json)
begin
if not CheckIfNumberSeriesIsGenerated(Json) then
exit;

CheckJsonTextProperty('seriesCode', Json, true);
CheckMaximumLengthOfPropertyValue('seriesCode', Json, 20);
CheckJsonTextProperty('description', Json, true);
CheckJsonTextProperty('startingNo', Json, true);
CheckMaximumLengthOfPropertyValue('startingNo', Json, 20);
CheckJsonTextProperty('endingNo', Json, false);
CheckMaximumLengthOfPropertyValue('endingNo', Json, 20);
CheckJsonTextProperty('warningNo', Json, false);
CheckMaximumLengthOfPropertyValue('warningNo', Json, 20);
CheckJsonIntegerProperty('incrementByNo', Json, false);
CheckJsonIntegerProperty('tableId', Json, true);
CheckJsonIntegerProperty('fieldId', Json, true);
end;

local procedure CheckIfNumberSeriesIsGenerated(var Json: Codeunit Json): Boolean
var
IsExists: Boolean;
begin
Json.GetBoolPropertyValueFromJObjectByName('exists', IsExists);
exit(not IsExists);
end;

local procedure CheckIfArrayIsNotEmpty(NumberOfGeneratedNoSeries: Integer)
begin
if NumberOfGeneratedNoSeries = 0 then
Error(EmptyCompletionErr);
end;

local procedure CheckTextPropertyExistAndCheckIfNotEmpty(propertyName: Text; var Json: Codeunit Json)
local procedure CheckJsonTextProperty(propertyName: Text; var Json: Codeunit Json; IsRequired: Boolean)
var
value: Text;
begin
Json.GetStringPropertyValueByName(propertyName, value);
if value = '' then
Error(IncorrectCompletionErr, propertyName);
if not IsRequired then
exit;

if value <> '' then
exit;

Error(IncorrectCompletionErr, propertyName);
end;

local procedure CheckIntegerPropertyExistAndCheckIfNotEmpty(propertyName: Text; var Json: Codeunit Json)
local procedure CheckJsonIntegerProperty(propertyName: Text; var Json: Codeunit Json; IsRequired: Boolean)
var
PropertyValue: Integer;
begin
Json.GetIntegerPropertyValueFromJObjectByName(propertyName, PropertyValue);
if PropertyValue = 0 then
Error(IncorrectCompletionErr, propertyName);
if not IsRequired then
exit;

if PropertyValue <> 0 then
exit;

Error(IncorrectCompletionErr, propertyName);
end;

local procedure CheckMaximumLengthOfPropertyValue(propertyName: Text; var Json: Codeunit Json; maxLength: Integer)
var
value: Text;
begin
Json.GetStringPropertyValueByName(propertyName, value);
if StrLen(value) > maxLength then
Error(TextLengthIsOverMaxLimitErr, propertyName, maxLength);
if StrLen(value) <= maxLength then
exit;

Error(TextLengthIsOverMaxLimitErr, propertyName, maxLength);
end;

local procedure ReadGeneratedNumberSeriesJArray(Completion: Text) NoSeriesJArray: JsonArray
Expand Down Expand Up @@ -445,14 +473,12 @@ codeunit 324 "No. Series Copilot Impl."
var
NoSeriesCode: Text;
NoSeriesObj: Text;
IsExists: Boolean;
begin
Json.GetObjectFromCollectionByIndex(i, NoSeriesObj);
Json.InitializeObject(NoSeriesObj);
Json.GetBoolPropertyValueFromJObjectByName('exists', IsExists);
Json.GetStringPropertyValueByName('seriesCode', NoSeriesCode);

if NoSeriesCodes.Contains(NoSeriesCode) and (not IsExists) then begin
if NoSeriesCodes.Contains(NoSeriesCode) and (CheckIfNumberSeriesIsGenerated(Json)) then begin
Json.RemoveJObjectFromCollection(i);
exit;
end;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,23 +114,23 @@ page 332 "No. Series Generation"
{
Caption = 'Prepare for next year';

action(SetupForNextYear)
action(PrepareForNextYear)
{
Caption = 'Set up number series for the next year';
Caption = 'Prepare number series for the next year';
ToolTip = 'Sample prompt for setting up number series for the next year.';
trigger OnAction()
begin
InputText := SetupForNextYearLbl;
InputText := PrepareForNextYearLbl;
CurrPage.Update();
end;
}
action(SetupModuleForNextYear)
action(PrepareModuleForNextYear)
{
Caption = 'Set up number series for the [sales] module for the next year';
Caption = 'Prepare number series for the [sales] module for the next year';
ToolTip = 'Sample prompt for setting up number series for a specific module for the next year. Replace [sales] with the module you want to set up number series for.';
trigger OnAction()
begin
InputText := SetupModuleForNextYearLbl;
InputText := PrepareModuleForNextYearLbl;
CurrPage.Update();
end;
}
Expand Down Expand Up @@ -178,8 +178,8 @@ page 332 "No. Series Generation"
CreateNoSeriesForModuleWithPatternLbl: Label 'Create number series for [specify here] module in the format ';
CreateNoSeriesForCompanyLbl: Label 'Create numbers series for the new company';
ChangeNumberLbl: Label 'Change the [specify here] number to ';
SetupForNextYearLbl: Label 'Set up number series for the next year';
SetupModuleForNextYearLbl: Label 'Set up number series for the [specify here] module for the next year';
PrepareForNextYearLbl: Label 'Prepare number series for the next year';
PrepareModuleForNextYearLbl: Label 'Prepare number series for the [specify here] module for the next year';

trigger OnAfterGetCurrRecord()
begin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@

namespace Microsoft.Foundation.NoSeries;

codeunit 337 "Record Match Impl."
codeunit 337 "No. Series Text Match Impl."

Check failure on line 8 in src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Search/Classic/NoSeriesTextMatchImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build Business Foundation (Clean) / Business Foundation (Clean)

AL0679 The application object 'Codeunit Microsoft.Foundation.NoSeries."No. Series Text Match Impl."' is not included in any entitlement and will therefore not be accessible in the cloud.

Check failure on line 8 in src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Search/Classic/NoSeriesTextMatchImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build Business Foundation (Translated) / Business Foundation (Translated)

AL0679 The application object 'Codeunit Microsoft.Foundation.NoSeries."No. Series Text Match Impl."' is not included in any entitlement and will therefore not be accessible in the cloud.

Check failure on line 8 in src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Search/Classic/NoSeriesTextMatchImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build Business Foundation (Default) / Business Foundation (Default)

AL0679 The application object 'Codeunit Microsoft.Foundation.NoSeries."No. Series Text Match Impl."' is not included in any entitlement and will therefore not be accessible in the cloud.
{
Access = Internal;
InherentPermissions = X;
InherentEntitlements = X;

procedure IsRelevant(FirstString: Text; SecondString: Text): Boolean
var
Score: Decimal;
begin
FirstString := RemoveShortWords(FirstString);

Check failure on line 16 in src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Search/Classic/NoSeriesTextMatchImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Default) / System Application Modules (Default)

AA0139 Possible overflow assigning 'Text' to 'Text[250]'.

Check failure on line 16 in src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Search/Classic/NoSeriesTextMatchImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Clean) / System Application Modules (Clean)

AA0139 Possible overflow assigning 'Text' to 'Text[250]'.
SecondString := RemoveShortWords(SecondString);

Check failure on line 17 in src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Search/Classic/NoSeriesTextMatchImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Default) / System Application Modules (Default)

AA0139 Possible overflow assigning 'Text' to 'Text[250]'.

Check failure on line 17 in src/Business Foundation/App/NoSeriesCopilot/src/Copilot/Search/Classic/NoSeriesTextMatchImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Clean) / System Application Modules (Clean)

AA0139 Possible overflow assigning 'Text' to 'Text[250]'.

Score := CalculateStringNearness(FirstString, SecondString, GetMatchLengthTreshold(), 100) / 100;

exit(Score >= RequiredNearness());
end;

/// <summary>
/// Computes a nearness score between strings. Nearness is based on repeatedly finding longest common substrings.
Expand All @@ -21,7 +31,7 @@ codeunit 337 "Record Match Impl."
/// <param name="Threshold">Substring matches below Threshold are not considered</param>
/// <param name="NormalizingFactor">Max value returned by this procedure</param>
/// <returns>A number between 0 and NormalizingFactor, representing how much of the strings was matched</returns>
procedure CalculateStringNearness(FirstString: Text; SecondString: Text; Threshold: Integer; NormalizingFactor: Integer): Integer
local procedure CalculateStringNearness(FirstString: Text; SecondString: Text; Threshold: Integer; NormalizingFactor: Integer): Integer
var
Result: Text;
TotalMatchedChars: Integer;
Expand Down Expand Up @@ -98,7 +108,7 @@ codeunit 337 "Record Match Impl."
exit(MinThreshold <= Length);
end;

procedure RemoveShortWords(OriginalText: Text[250]): Text[250];
local procedure RemoveShortWords(OriginalText: Text[250]): Text[250];
var
Words: List of [Text];
Word: Text[250];
Expand All @@ -112,4 +122,14 @@ codeunit 337 "Record Match Impl."
OriginalText := Result; // assign the result back to the text parameter
exit(OriginalText);
end;

local procedure GetMatchLengthTreshold(): Decimal
begin
exit(2);
end;

local procedure RequiredNearness(): Decimal
begin
exit(0.9)
end;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ codeunit 331 "No. Series Cop. Add Intent" implements "AOAI Function"
NumberOfAddedTablesPlaceholderLbl: Label '{number_of_tables}', Locked = true;
TelemetryTool1PromptRetrievalErr: Label 'Unable to retrieve the prompt for No. Series Copilot Tool 1 from Azure Key Vault.', Locked = true;
TelemetryTool1DefinitionRetrievalErr: Label 'Unable to retrieve the definition for No. Series Copilot Tool 1 from Azure Key Vault.', Locked = true;
ToolProgressDialogTextLbl: Label 'Searching for tables with number series related to your query';
ToolLoadingErr: Label 'Unable to load the No. Series Copilot Tool 1. Please try again later.';
ExistingNoSeriesMessageLbl: Label 'Number series already configured. If you wish to modify the existing series, please use the `Modify number series` prompt.';

Expand Down Expand Up @@ -62,7 +63,9 @@ codeunit 331 "No. Series Cop. Add Intent" implements "AOAI Function"
NewNoSeriesPrompt, CustomPatternsPromptList, TablesYamlList, EmptyList : List of [Text];
NumberOfToolResponses, i, ActualTablesChunkSize : Integer;
NumberOfAddedTables: Integer;
Progress: Dialog;
begin
Progress.Open(ToolProgressDialogTextLbl);
GetTablesRequireNoSeries(Arguments, TempSetupTable, TempNoSeriesField);
ToolsImpl.GetUserSpecifiedOrExistingNumberPatternsGuidelines(Arguments, CustomPatternsPromptList, EmptyList, false);

Expand All @@ -80,7 +83,8 @@ codeunit 331 "No. Series Cop. Add Intent" implements "AOAI Function"
.Replace(NumberOfAddedTablesPlaceholderLbl, Format(ActualTablesChunkSize)));

ToolResults.Add(ToolsImpl.ConvertListToText(NewNoSeriesPrompt), ActualTablesChunkSize);
end
end;
Progress.Close();
end;

local procedure GetTablesRequireNoSeries(var Arguments: JsonObject; var TempSetupTable: Record "Table Metadata" temporary; var TempNoSeriesField: Record "Field" temporary)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ codeunit 334 "No. Series Cop. Change Intent" implements "AOAI Function"
NumberOfAddedTablesPlaceholderLbl: Label '{number_of_tables}', Locked = true;
TelemetryTool2PromptRetrievalErr: Label 'Unable to retrieve the prompt for No. Series Copilot Tool 2 from Azure Key Vault.', Locked = true;
TelemetryTool2DefinitionRetrievalErr: Label 'Unable to retrieve the definition for No. Series Copilot Tool 2 from Azure Key Vault.', Locked = true;
ToolProgressDialogTextLbl: Label 'Searching for tables with number series related to your query';
ToolLoadingErr: Label 'Unable to load the No. Series Copilot Tool 2. Please try again later.';

procedure GetName(): Text
Expand Down Expand Up @@ -71,12 +72,14 @@ codeunit 334 "No. Series Cop. Change Intent" implements "AOAI Function"
ChangeNoSeriesPrompt, CustomPatternsPromptList, TablesYamlList, ExistingNoSeriesToChangeList : List of [Text];
NumberOfToolResponses, i, ActualTablesChunkSize : Integer;
NumberOfChangedTables: Integer;
Progress: Dialog;
begin
if not CheckIfUserSpecifiedNoSeriesToChange(Arguments) then begin
NoSeriesCopilotImpl.SendNotification(GetLastErrorText());
exit;
end;

Progress.Open(ToolProgressDialogTextLbl);
GetTablesWithNoSeries(Arguments, TempSetupTable, TempNoSeriesField, ExistingNoSeriesToChangeList);
ToolsImpl.GetUserSpecifiedOrExistingNumberPatternsGuidelines(Arguments, CustomPatternsPromptList, ExistingNoSeriesToChangeList, UpdateForNextYear);

Expand All @@ -97,7 +100,8 @@ codeunit 334 "No. Series Cop. Change Intent" implements "AOAI Function"
.Replace(NumberOfAddedTablesPlaceholderLbl, Format(ActualTablesChunkSize)));

ToolResults.Add(ToolsImpl.ConvertListToText(ChangeNoSeriesPrompt), ActualTablesChunkSize);
end
end;
Progress.Close();
end;

[TryFunction]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ codeunit 349 "No. Series Cop. Nxt Yr. Intent" implements "AOAI Function"

var
Telemetry: Codeunit Telemetry;
FunctionNameLbl: Label 'GenerateNextYearNumberSeries', Locked = true;
FunctionNameLbl: Label 'PrepareNextYearNumberSeries', Locked = true;
TelemetryTool3DefinitionRetrievalErr: Label 'Unable to retrieve the definition for No. Series Copilot Tool 3 from Azure Key Vault.', Locked = true;
ToolLoadingErr: Label 'Unable to load the No. Series Copilot Tool 3. Please try again later.';

Expand Down Expand Up @@ -45,7 +45,7 @@ codeunit 349 "No. Series Cop. Nxt Yr. Intent" implements "AOAI Function"
var
AzureKeyVault: Codeunit "Azure Key Vault";
begin
if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool3Definition', Definition) then begin
if not AzureKeyVault.GetAzureKeyVaultSecret('NoSeriesCopilotTool3DefinitionV2', Definition) then begin // TODO: As the prompt should be different for v25.0 and v25.1, we need to add a new secret for v25.1 and update the code to get the correct prompt based on the version
Telemetry.LogMessage('0000ND9', TelemetryTool3DefinitionRetrievalErr, Verbosity::Error, DataClassification::SystemMetadata);
Error(ToolLoadingErr);
end;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,32 +225,21 @@ codeunit 336 "No. Series Cop. Tools Impl."

procedure IsRelevant(TableMetadata: Record "Table Metadata"; Field: Record "Field"; Entities: List of [Text]): Boolean
var
RecordMatchMgtCopy: Codeunit "Record Match Impl.";
TextMatchImpl: Codeunit "No. Series Text Match Impl.";
Entity: Text[250];
String1: Text[250];
String2: Text[250];
Score: Decimal;
begin
foreach Entity in Entities do begin
String1 := RecordMatchMgtCopy.RemoveShortWords(RemoveTextPart(TableMetadata.Caption, ' Setup') + ' ' + RemoveTextParts(Field.FieldName, GetNoSeriesAbbreviations()));
String2 := RecordMatchMgtCopy.RemoveShortWords(Entity);
Score := RecordMatchMgtCopy.CalculateStringNearness(String1, String2, GetMatchLengthThreshold(), 100) / 100;
if Score >= RequiredNearness() then
String1 := RemoveTextPart(TableMetadata.Name, '& ') + ' ' + Field.FieldName;
String2 := Entity;

if TextMatchImpl.IsRelevant(String1, String2) then
exit(true);
end;
exit(false);
end;

local procedure GetMatchLengthThreshold(): Decimal
begin
exit(2);
end;

local procedure RequiredNearness(): Decimal
begin
exit(0.9)
end;

procedure GenerateChunkedTablesListInYamlFormat(var TablesYamlList: List of [Text]; var TempSetupTable: Record "Table Metadata" temporary; var TempNoSeriesField: Record "Field" temporary; var NumberOfAddedTables: Integer)
begin
Clear(TablesYamlList);
Expand Down

0 comments on commit 816520c

Please sign in to comment.