Delete self contained Thunder Tests
Back to .net8.0 api/v4/InfinityQS ApiExplorerSettings Wafer Counter Color Sorting
This commit is contained in:
@ -35,19 +35,13 @@ public class AppSettingsRepository : IAppSettingsRepository<Models.Binder.AppSet
|
||||
{
|
||||
if (string.IsNullOrEmpty(_AppSettings.ConnectionString))
|
||||
throw new NotSupportedException();
|
||||
if (string.IsNullOrEmpty(_AppSettings.OI2SqlConnectionString))
|
||||
throw new NotSupportedException();
|
||||
#if DEBUG
|
||||
if (!_AppSettings.ConnectionString.Contains("test", StringComparison.CurrentCultureIgnoreCase))
|
||||
throw new NotSupportedException();
|
||||
if (!_AppSettings.OI2SqlConnectionString.Contains("test", StringComparison.CurrentCultureIgnoreCase))
|
||||
throw new NotSupportedException();
|
||||
#endif
|
||||
#if !DEBUG
|
||||
if (_AppSettings.ConnectionString.Contains("test", StringComparison.CurrentCultureIgnoreCase))
|
||||
throw new NotSupportedException();
|
||||
if (_AppSettings.OI2SqlConnectionString.Contains("test", StringComparison.CurrentCultureIgnoreCase))
|
||||
throw new NotSupportedException();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -13,11 +13,7 @@ public class ClientSettingsRepository : IClientSettingsRepository
|
||||
|
||||
internal List<string> GetClientSettings(IPAddress? remoteIpAddress)
|
||||
{
|
||||
List<string> results = new();
|
||||
if (remoteIpAddress is null)
|
||||
results.Add(nameof(remoteIpAddress));
|
||||
else
|
||||
results.Add(remoteIpAddress.ToString());
|
||||
List<string> results = new() { remoteIpAddress is null ? nameof(remoteIpAddress) : remoteIpAddress.ToString() };
|
||||
if (!_AppSettings.IsDevelopment)
|
||||
throw new Exception("Shouldn't expose!");
|
||||
return results;
|
||||
|
@ -30,7 +30,7 @@ public class InfinityQSV3Repository : IInfinityQSV3Repository
|
||||
}
|
||||
|
||||
string IInfinityQSV3Repository.GetCommandText(string subGroupId)
|
||||
{
|
||||
{ // cSpell:disable
|
||||
StringBuilder result = new();
|
||||
if (string.IsNullOrEmpty(subGroupId))
|
||||
throw new ArgumentException(null, nameof(subGroupId));
|
||||
@ -50,10 +50,10 @@ public class InfinityQSV3Repository : IInfinityQSV3Repository
|
||||
_ = result.Append(" where sd.f_sgrp = ").Append(subGroupId).AppendLine(" ");
|
||||
_ = result.AppendLine(" for json path ");
|
||||
return result.ToString();
|
||||
}
|
||||
} // cSpell:enable
|
||||
|
||||
string IInfinityQSV3Repository.GetCommandText(string? subGroupId, string? process, string? job, string? part, string? lot, string? dateTime)
|
||||
{
|
||||
{ // cSpell:disable
|
||||
StringBuilder result = new();
|
||||
const string dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
|
||||
if (!string.IsNullOrEmpty(dateTime) && (dateTime.Contains('-') || dateTime.Contains(' ') || dateTime.Contains(':')) && dateTime.Length != dateTimeFormat.Length)
|
||||
@ -144,7 +144,7 @@ public class InfinityQSV3Repository : IInfinityQSV3Repository
|
||||
.AppendLine(" iq.td_test ")
|
||||
.AppendLine(" for json path ");
|
||||
return result.ToString();
|
||||
}
|
||||
} // cSpell:enable
|
||||
|
||||
private static StringBuilder GetForJsonPath(IDbConnectionFactory dbConnectionFactory, string commandText)
|
||||
{
|
||||
@ -226,7 +226,7 @@ public class InfinityQSV3Repository : IInfinityQSV3Repository
|
||||
}
|
||||
|
||||
string IInfinityQSV3Repository.GetCommandText(InfinityQSV3 infinityQSV3)
|
||||
{
|
||||
{ // cSpell:disable
|
||||
StringBuilder result = new();
|
||||
if (string.IsNullOrEmpty(infinityQSV3.Process))
|
||||
throw new ArgumentException(nameof(infinityQSV3.Process));
|
||||
@ -254,7 +254,7 @@ public class InfinityQSV3Repository : IInfinityQSV3Repository
|
||||
.Append(" and ev.f_sgtm = ").Append(infinityQSV3.SubGroupDateTime).AppendLine(" ")
|
||||
.AppendLine(" for json path ");
|
||||
return result.ToString();
|
||||
}
|
||||
} // cSpell:enable
|
||||
|
||||
Result<InfinityQSV3[]> IInfinityQSV3Repository.GetHeader(string subGroupId)
|
||||
{
|
||||
@ -281,7 +281,7 @@ public class InfinityQSV3Repository : IInfinityQSV3Repository
|
||||
}
|
||||
|
||||
string IInfinityQSV3Repository.GetCommandText(string process, string? part)
|
||||
{
|
||||
{ // cSpell:disable
|
||||
StringBuilder result = new();
|
||||
if (string.IsNullOrEmpty(process))
|
||||
throw new ArgumentException(null, nameof(process));
|
||||
@ -304,7 +304,7 @@ public class InfinityQSV3Repository : IInfinityQSV3Repository
|
||||
.Append(" and part.f_name = '").Append(part).AppendLine("' ")
|
||||
.AppendLine(" for json path; ");
|
||||
return result.ToString();
|
||||
}
|
||||
} // cSpell:enable
|
||||
|
||||
string IInfinityQSV3Repository.GetProductDataAverageSumOfDefectsProcessMeanProcessSigma(string process, string? recipe)
|
||||
{
|
||||
@ -363,7 +363,7 @@ public class InfinityQSV3Repository : IInfinityQSV3Repository
|
||||
}
|
||||
|
||||
string IInfinityQSV3Repository.GetCommandText(List<string> eppReactorNumbers)
|
||||
{
|
||||
{ // cSpell:disable
|
||||
StringBuilder result = new();
|
||||
_ = result
|
||||
.AppendLine(" select se.f_sgrp, ")
|
||||
@ -411,7 +411,7 @@ public class InfinityQSV3Repository : IInfinityQSV3Repository
|
||||
.AppendLine(" order by iq.pr_name ")
|
||||
.AppendLine(" for json path; ");
|
||||
return result.ToString();
|
||||
}
|
||||
} // cSpell:enable
|
||||
|
||||
private static List<string> Convert(int[] night)
|
||||
{
|
||||
@ -484,17 +484,16 @@ public class InfinityQSV3Repository : IInfinityQSV3Repository
|
||||
continue;
|
||||
used.Add(i);
|
||||
found = false;
|
||||
timeSpan = new(ticks - infinityQS1090FullLoad.SubGroupIdFormated.Ticks);
|
||||
timeSpan = new(ticks - infinityQS1090FullLoad.SubGroupIdFormatted.Ticks);
|
||||
loadedCount = reactor.LoadedRDS is null ? 0 : reactor.LoadedRDS.Count;
|
||||
loadedRDS = reactor.LoadedRDS is null ? " " : reactor.LoadedRDS[0].ToString();
|
||||
results.Add(new string[]
|
||||
{
|
||||
results.Add(new string[]{
|
||||
reactor.ReactorNo.ToString(),
|
||||
reactor.E10State,
|
||||
loadedRDS,
|
||||
infinityQS1090FullLoad.Value.ToString(),
|
||||
infinityQS1090FullLoad.TemperatureOffsetPercentage.ToString(),
|
||||
infinityQS1090FullLoad.SubGroupIdFormated.ToString(),
|
||||
infinityQS1090FullLoad.SubGroupIdFormatted.ToString(),
|
||||
Math.Floor(timeSpan.TotalHours).ToString()
|
||||
});
|
||||
for (int j = i + 1; j < infinityQS1090FullLoads.Length; j++)
|
||||
@ -510,21 +509,21 @@ public class InfinityQSV3Repository : IInfinityQSV3Repository
|
||||
continue;
|
||||
used.Add(j);
|
||||
found = true;
|
||||
timeSpan = new(ticks - infinityQS1090FullLoad.SubGroupIdFormated.Ticks);
|
||||
timeSpan = new(ticks - infinityQS1090FullLoad.SubGroupIdFormatted.Ticks);
|
||||
loadedCount = reactor.LoadedRDS is null ? 0 : reactor.LoadedRDS.Count;
|
||||
loadedRDS = reactor.LoadedRDS is null ? " " : reactor.LoadedRDS[0].ToString();
|
||||
columns = new();
|
||||
columns.AddRange(results[^1]);
|
||||
columns.AddRange(new string[]
|
||||
{
|
||||
" ",
|
||||
reactor.ReactorNo.ToString(),
|
||||
reactor.E10State,
|
||||
loadedRDS,
|
||||
infinityQS1090FullLoad.Value.ToString(),
|
||||
infinityQS1090FullLoad.TemperatureOffsetPercentage.ToString(),
|
||||
infinityQS1090FullLoad.SubGroupIdFormated.ToString(),
|
||||
Math.Floor(timeSpan.TotalHours).ToString()
|
||||
" ",
|
||||
reactor.ReactorNo.ToString(),
|
||||
reactor.E10State,
|
||||
loadedRDS,
|
||||
infinityQS1090FullLoad.Value.ToString(),
|
||||
infinityQS1090FullLoad.TemperatureOffsetPercentage.ToString(),
|
||||
infinityQS1090FullLoad.SubGroupIdFormatted.ToString(),
|
||||
Math.Floor(timeSpan.TotalHours).ToString()
|
||||
});
|
||||
results[^1] = columns.ToArray();
|
||||
break;
|
||||
@ -556,8 +555,8 @@ public class InfinityQSV3Repository : IInfinityQSV3Repository
|
||||
foreach (string[] row in collection)
|
||||
{
|
||||
_ = result.Append("<tr>");
|
||||
foreach (string coulmn in row)
|
||||
_ = result.Append("<td>").Append(coulmn).Append("</td>");
|
||||
foreach (string column in row)
|
||||
_ = result.Append("<td>").Append(column).Append("</td>");
|
||||
_ = result.Append("</tr>");
|
||||
}
|
||||
return result.ToString();
|
||||
|
783
Server/Repositories/InfinityQSV4Repository.cs
Normal file
783
Server/Repositories/InfinityQSV4Repository.cs
Normal file
@ -0,0 +1,783 @@
|
||||
using OI.Metrology.Server.Models;
|
||||
using OI.Metrology.Shared.DataModels;
|
||||
using OI.Metrology.Shared.Models;
|
||||
using OI.Metrology.Shared.Models.Stateless;
|
||||
using OI.Metrology.Shared.Repositories;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace OI.Metrology.Server.Repository;
|
||||
|
||||
public class InfinityQSV4Repository : IInfinityQSV4Repository
|
||||
{
|
||||
|
||||
private record Record(string? Color, string Text);
|
||||
|
||||
private readonly string _MockRoot;
|
||||
private readonly string _RepositoryName;
|
||||
private readonly AppSettings _AppSettings;
|
||||
private readonly IHttpClientFactory _HttpClientFactory;
|
||||
private readonly IDbConnectionFactory _DBConnectionFactory;
|
||||
private readonly string _OpenInsightApplicationProgrammingInterface;
|
||||
|
||||
public InfinityQSV4Repository(AppSettings appSettings, IDbConnectionFactory dbConnectionFactory, IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_AppSettings = appSettings;
|
||||
_MockRoot = appSettings.MockRoot;
|
||||
_HttpClientFactory = httpClientFactory;
|
||||
_DBConnectionFactory = dbConnectionFactory;
|
||||
_RepositoryName = nameof(InfinityQSV4Repository)[..^10];
|
||||
_OpenInsightApplicationProgrammingInterface = appSettings.OpenInsightApplicationProgrammingInterface;
|
||||
}
|
||||
|
||||
string IInfinityQSV4Repository.GetCommandText(string subGroupId)
|
||||
{ // cSpell:disable
|
||||
StringBuilder result = new();
|
||||
if (string.IsNullOrEmpty(subGroupId))
|
||||
throw new ArgumentException(null, nameof(subGroupId));
|
||||
_ = result
|
||||
.AppendLine(" select ")
|
||||
.AppendLine(" sd.f_sgrp sd_sgrp, ")
|
||||
.AppendLine(" sd.f_tsno sd_tsno, ")
|
||||
.AppendLine(" dd.f_dsgp dd_dsgp, ")
|
||||
.AppendLine(" dg.f_name gd_name, ")
|
||||
.AppendLine(" dd.f_name dd_name ")
|
||||
.AppendLine(" from [SPCEPIWORLD].[dbo].[SGRP_DSC] sd ")
|
||||
.AppendLine(" join [SPCEPIWORLD].[dbo].[DESC_DAT] dd ")
|
||||
.AppendLine(" on sd.f_dsgp = dd.f_dsgp ")
|
||||
.AppendLine(" and sd.f_desc = dd.f_desc ")
|
||||
.AppendLine(" join [SPCEPIWORLD].[dbo].[DESC_GRP] dg ")
|
||||
.AppendLine(" on dd.f_dsgp = dg.f_dsgp ");
|
||||
_ = result.Append(" where sd.f_sgrp = ").Append(subGroupId).AppendLine(" ");
|
||||
_ = result.AppendLine(" for json path ");
|
||||
return result.ToString();
|
||||
} // cSpell:enable
|
||||
|
||||
string IInfinityQSV4Repository.GetCommandText(string? subGroupId, string? process, string? job, string? part, string? lot, string? dateTime)
|
||||
{ // cSpell:disable
|
||||
StringBuilder result = new();
|
||||
const string dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
|
||||
if (!string.IsNullOrEmpty(dateTime) && (dateTime.Contains('-') || dateTime.Contains(' ') || dateTime.Contains(':')) && dateTime.Length != dateTimeFormat.Length)
|
||||
throw new ArgumentException(null, nameof(dateTime));
|
||||
_ = result
|
||||
.AppendLine(" select case when iq.sl_loos is null then 0 else iq.sl_loos end + ")
|
||||
.AppendLine(" case when iq.sl_uoos is null then 0 else iq.sl_uoos end + ")
|
||||
.AppendLine(" iq.ev_count as iq_sum, ")
|
||||
.AppendLine(" iq.sl_aflag, ")
|
||||
.AppendLine(" iq.sl_loos, ")
|
||||
.AppendLine(" iq.sl_uoos, ")
|
||||
.AppendLine(" iq.se_sgrp, ")
|
||||
.AppendLine(" iq.se_sgtm, ")
|
||||
.AppendLine(" iq.se_tsno, ")
|
||||
.AppendLine(" iq.td_test, ")
|
||||
.AppendLine(" iq.pr_name, ")
|
||||
.AppendLine(" iq.jd_name, ")
|
||||
.AppendLine(" iq.pl_name, ")
|
||||
.AppendLine(" iq.pd_name, ")
|
||||
.AppendLine(" iq.td_name, ")
|
||||
.AppendLine(" iq.se_val, ")
|
||||
.AppendLine(" iq.sl_eflag, ")
|
||||
.AppendLine(" iq.sl_scal, ")
|
||||
.AppendLine(" iq.sl_sls, ")
|
||||
.AppendLine(" iq.sl_usl ")
|
||||
.AppendLine(" from ( ")
|
||||
.AppendLine(" select ")
|
||||
.AppendLine(" se.f_sgrp se_sgrp, ")
|
||||
.AppendLine(" se.f_sgtm se_sgtm, ")
|
||||
.AppendLine(" se.f_tsno se_tsno, ")
|
||||
.AppendLine(" se.f_val se_val, ")
|
||||
.AppendLine(" pr.f_name pr_name, ")
|
||||
.AppendLine(" jd.f_name jd_name, ")
|
||||
.AppendLine(" pl.f_name pl_name, ")
|
||||
.AppendLine(" pd.f_name pd_name, ")
|
||||
.AppendLine(" td.f_test td_test, ")
|
||||
.AppendLine(" td.f_name td_name, ")
|
||||
.AppendLine(" sl.f_eflag sl_eflag, ")
|
||||
.AppendLine(" sl.f_aflag sl_aflag, ")
|
||||
.AppendLine(" sl.f_scal sl_scal, ")
|
||||
.AppendLine(" sl.f_lsl sl_sls, ")
|
||||
.AppendLine(" sl.f_usl sl_usl, ")
|
||||
.AppendLine(" case when sl.f_aflag is null or sl.f_aflag = 0 then null else ")
|
||||
.AppendLine(" case when round(se.f_val, sl.F_scal, 1) < sl.f_lsl then 1 else 0 end ")
|
||||
.AppendLine(" end as sl_loos, ")
|
||||
.AppendLine(" case when sl.f_aflag is null or sl.f_aflag = 0 then null else ")
|
||||
.AppendLine(" case when round(se.f_val, sl.F_scal, 1) > sl.f_usl then 1 else 0 end ")
|
||||
.AppendLine(" end as sl_uoos, ")
|
||||
.AppendLine(" (select count(ev.f_evnt) ")
|
||||
.AppendLine(" from [spcepiworld].[dbo].[evnt_inf] ev ")
|
||||
.AppendLine(" where ev.f_prcs = pr.f_prcs ")
|
||||
.AppendLine(" and ev.f_part = pd.f_part ")
|
||||
.AppendLine(" and ev.f_sgtm = se.f_sgtm ")
|
||||
.AppendLine(" ) ev_count ")
|
||||
.AppendLine(" from [spcepiworld].[dbo].[sgrp_ext] se ")
|
||||
.AppendLine(" join [spcepiworld].[dbo].[prcs_dat] pr ")
|
||||
.AppendLine(" on se.f_prcs = pr.f_prcs ")
|
||||
.AppendLine(" join [spcepiworld].[dbo].[job_dat] jd ")
|
||||
.AppendLine(" on se.f_job = jd.f_job ")
|
||||
.AppendLine(" join [spcepiworld].[dbo].[part_lot] pl ")
|
||||
.AppendLine(" on se.f_lot = pl.f_lot ")
|
||||
.AppendLine(" join [spcepiworld].[dbo].[part_dat] pd ")
|
||||
.AppendLine(" on se.f_part = pd.f_part ")
|
||||
.AppendLine(" join [spcepiworld].[dbo].[test_dat] td ")
|
||||
.AppendLine(" on se.f_test = td.f_test ")
|
||||
.AppendLine(" left join [spcepiworld].[dbo].[spec_lim] sl ")
|
||||
.AppendLine(" on se.f_part = sl.f_part ")
|
||||
.AppendLine(" and se.f_test = sl.f_test ")
|
||||
.AppendLine(" where se.f_flag = 0 ")
|
||||
.AppendLine(" and (sl.f_prcs is null or se.f_prcs = sl.f_prcs or sl.f_prcs = 0) ");
|
||||
if (!string.IsNullOrEmpty(subGroupId))
|
||||
_ = result.Append(" and se.f_sgrp = ").Append(subGroupId.Split(" ")[0]).AppendLine(" ");
|
||||
if (!string.IsNullOrEmpty(process))
|
||||
_ = result.Append(" and pr.f_name = '").Append(process).AppendLine("' ");
|
||||
if (!string.IsNullOrEmpty(part))
|
||||
_ = result.Append(" and pd.f_name = '").Append(part).AppendLine("' ");
|
||||
if (!string.IsNullOrEmpty(job))
|
||||
_ = result.Append(" and jd.f_name = '").Append(job).AppendLine("' ");
|
||||
if (!string.IsNullOrEmpty(lot))
|
||||
_ = result.Append(" and pl.f_name = '").Append(lot).AppendLine("' ");
|
||||
if (!string.IsNullOrEmpty(dateTime) && (dateTime.Contains('-') || dateTime.Contains(' ') || dateTime.Contains(':')))
|
||||
_ = result.Append(" and dateadd(HH, -7, (dateadd(SS, convert(bigint, se.f_sgtm), '19700101'))) = '").Append(dateTime).AppendLine("' ");
|
||||
_ = result.AppendLine(" ) as iq ")
|
||||
.AppendLine(" order by iq.sl_loos + iq.sl_uoos + iq.ev_count desc, ")
|
||||
.AppendLine(" iq.sl_aflag desc, ")
|
||||
.AppendLine(" iq.se_sgrp, ")
|
||||
.AppendLine(" iq.se_tsno, ")
|
||||
.AppendLine(" iq.td_test ")
|
||||
.AppendLine(" for json path ");
|
||||
return result.ToString();
|
||||
} // cSpell:enable
|
||||
|
||||
private static StringBuilder GetForJsonPath(IDbConnectionFactory dbConnectionFactory, string commandText, bool useIqsConnection)
|
||||
{
|
||||
StringBuilder stringBuilder = new();
|
||||
using DbConnection dbConnection = dbConnectionFactory.GetDbConnection(useIqsConnection);
|
||||
DbCommand dbCommand = dbConnection.CreateCommand();
|
||||
dbCommand.CommandText = commandText;
|
||||
DbDataReader dbDataReader = dbCommand.ExecuteReader(CommandBehavior.SequentialAccess);
|
||||
while (dbDataReader.Read())
|
||||
_ = stringBuilder.Append(dbDataReader.GetString(0));
|
||||
return stringBuilder;
|
||||
}
|
||||
|
||||
private static InfinityQSV4 GetInfinityQSV4(IDbConnectionFactory dbConnectionFactory, IInfinityQSV4Repository infinityQSV4Repository, string subGroupId)
|
||||
{
|
||||
InfinityQSV4 result;
|
||||
string commandText = infinityQSV4Repository.GetCommandText(subGroupId, process: string.Empty, job: string.Empty, part: string.Empty, lot: string.Empty, dateTime: string.Empty);
|
||||
StringBuilder stringBuilder = GetForJsonPath(dbConnectionFactory, commandText, useIqsConnection: false);
|
||||
InfinityQSV4[]? results = stringBuilder.Length == 0 ? Array.Empty<InfinityQSV4>() : JsonSerializer.Deserialize(stringBuilder.ToString(), ResultInfinityQSV4SourceGenerationContext.Default.InfinityQSV4Array); // , new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
|
||||
if (results is null)
|
||||
throw new NullReferenceException(nameof(results));
|
||||
result = results.First();
|
||||
return result;
|
||||
}
|
||||
|
||||
Result<InfinityQSV4[]> IInfinityQSV4Repository.GetData(string subGroupId)
|
||||
{
|
||||
Result<InfinityQSV4[]>? result;
|
||||
if (!string.IsNullOrEmpty(_MockRoot))
|
||||
{
|
||||
string json = File.ReadAllText(Path.Combine(string.Concat(AppContext.BaseDirectory, _MockRoot), $"{_RepositoryName}-{nameof(IInfinityQSV4Repository.GetData)}.json"));
|
||||
result = JsonSerializer.Deserialize(json, ResultInfinityQSV4SourceGenerationContext.Default.ResultInfinityQSV4Array);
|
||||
if (result is null)
|
||||
throw new NullReferenceException(nameof(result));
|
||||
}
|
||||
else
|
||||
{
|
||||
IInfinityQSV4Repository infinityQSV4Repository = this;
|
||||
InfinityQSV4 infinityQSV4 = GetInfinityQSV4(_DBConnectionFactory, infinityQSV4Repository, subGroupId);
|
||||
GetOI(infinityQSV4);
|
||||
string commandText = infinityQSV4Repository.GetCommandText(subGroupId, process: infinityQSV4.Process, job: infinityQSV4.Job, part: infinityQSV4.Part, lot: infinityQSV4.Lot, dateTime: string.Concat(infinityQSV4.SubGroupDateTime));
|
||||
StringBuilder stringBuilder = GetForJsonPath(_DBConnectionFactory, commandText, useIqsConnection: false);
|
||||
InfinityQSV4[]? results = stringBuilder.Length == 0 ? Array.Empty<InfinityQSV4>() : JsonSerializer.Deserialize(stringBuilder.ToString(), ResultInfinityQSV4SourceGenerationContext.Default.InfinityQSV4Array); // , new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
|
||||
if (results is null)
|
||||
throw new NullReferenceException(nameof(results));
|
||||
result = new()
|
||||
{
|
||||
Results = results,
|
||||
TotalRows = results.Length,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Result<InfinityQSDescriptorV4[]> IInfinityQSV4Repository.GetDescriptors(string subGroupId)
|
||||
{
|
||||
Result<InfinityQSDescriptorV4[]>? result;
|
||||
if (!string.IsNullOrEmpty(_MockRoot))
|
||||
{
|
||||
string json = File.ReadAllText(Path.Combine(string.Concat(AppContext.BaseDirectory, _MockRoot), $"{_RepositoryName}-{nameof(IInfinityQSV4Repository.GetDescriptors)}.json"));
|
||||
result = JsonSerializer.Deserialize(json, ResultInfinityQSDescriptorV4SourceGenerationContext.Default.ResultInfinityQSDescriptorV4Array);
|
||||
if (result is null)
|
||||
throw new NullReferenceException(nameof(result));
|
||||
}
|
||||
else
|
||||
{
|
||||
IInfinityQSV4Repository infinityQSV4Repository = this;
|
||||
string commandText = infinityQSV4Repository.GetCommandText(subGroupId);
|
||||
StringBuilder stringBuilder = GetForJsonPath(_DBConnectionFactory, commandText, useIqsConnection: false);
|
||||
InfinityQSDescriptorV4[]? results = stringBuilder.Length == 0 ? Array.Empty<InfinityQSDescriptorV4>() : JsonSerializer.Deserialize(stringBuilder.ToString(), ResultInfinityQSDescriptorV4SourceGenerationContext.Default.InfinityQSDescriptorV4Array); // , new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
|
||||
if (results is null)
|
||||
throw new NullReferenceException(nameof(results));
|
||||
result = new()
|
||||
{
|
||||
Results = results,
|
||||
TotalRows = results.Length,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
string IInfinityQSV4Repository.GetCommandText(InfinityQSV4 infinityQSV4)
|
||||
{ // cSpell:disable
|
||||
StringBuilder result = new();
|
||||
if (string.IsNullOrEmpty(infinityQSV4.Process))
|
||||
throw new ArgumentException(nameof(infinityQSV4.Process));
|
||||
if (string.IsNullOrEmpty(infinityQSV4.Part))
|
||||
throw new ArgumentException(nameof(infinityQSV4.Part));
|
||||
_ = result
|
||||
.AppendLine(" select ")
|
||||
.AppendLine(" ev.f_evnt [ev_evnt], ")
|
||||
.AppendLine(" ev.f_sgtm [ev_sgtm], ")
|
||||
.AppendLine(" dateadd(HH, -7, (dateadd(SS, convert(bigint, ev.f_sgtm), '19700101'))) [ev_utc7], ")
|
||||
.AppendLine(" pr.f_name [pr_name], ")
|
||||
.AppendLine(" pd.f_name [pd_name], ")
|
||||
.AppendLine(" td.f_test [td_test], ")
|
||||
.AppendLine(" td.f_name [td_name], ")
|
||||
.AppendLine(" ev.f_name [ev_name] ")
|
||||
.AppendLine(" from [spcepiworld].[dbo].[evnt_inf] ev ")
|
||||
.AppendLine(" join [spcepiworld].[dbo].[prcs_dat] pr ")
|
||||
.AppendLine(" on ev.f_prcs = pr.f_prcs ")
|
||||
.AppendLine(" join [spcepiworld].[dbo].[part_dat] pd ")
|
||||
.AppendLine(" on ev.f_part = pd.f_part ")
|
||||
.AppendLine(" join [spcepiworld].[dbo].[test_dat] td ")
|
||||
.AppendLine(" on ev.f_test = td.f_test ")
|
||||
.Append(" where pr.f_name = '").Append(infinityQSV4.Process).AppendLine("' ")
|
||||
.Append(" and pd.f_name = '").Append(infinityQSV4.Part).AppendLine("' ")
|
||||
.Append(" and ev.f_sgtm = ").Append(infinityQSV4.SubGroupDateTime).AppendLine(" ")
|
||||
.AppendLine(" for json path ");
|
||||
return result.ToString();
|
||||
} // cSpell:enable
|
||||
|
||||
Result<InfinityQSV4[]> IInfinityQSV4Repository.GetHeader(string subGroupId)
|
||||
{
|
||||
Result<InfinityQSV4[]>? result;
|
||||
if (!string.IsNullOrEmpty(_MockRoot))
|
||||
{
|
||||
string json = File.ReadAllText(Path.Combine(string.Concat(AppContext.BaseDirectory, _MockRoot), $"{_RepositoryName}-{nameof(IInfinityQSV4Repository.GetHeader)}.json"));
|
||||
result = JsonSerializer.Deserialize(json, ResultInfinityQSV4SourceGenerationContext.Default.ResultInfinityQSV4Array);
|
||||
if (result is null)
|
||||
throw new NullReferenceException(nameof(result));
|
||||
}
|
||||
else
|
||||
{
|
||||
IInfinityQSV4Repository infinityQSV4Repository = this;
|
||||
InfinityQSV4 infinityQSV4 = GetInfinityQSV4(_DBConnectionFactory, infinityQSV4Repository, subGroupId);
|
||||
InfinityQSV4[] results = new InfinityQSV4[] { infinityQSV4 };
|
||||
result = new()
|
||||
{
|
||||
Results = results,
|
||||
TotalRows = results.Length,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
string IInfinityQSV4Repository.GetCommandText(string process, string? part)
|
||||
{ // cSpell:disable
|
||||
StringBuilder result = new();
|
||||
if (string.IsNullOrEmpty(process))
|
||||
throw new ArgumentException(null, nameof(process));
|
||||
if (string.IsNullOrEmpty(part))
|
||||
throw new ArgumentException(null, nameof(part));
|
||||
_ = result
|
||||
.AppendLine(" select [f_mean] as ProcessMean, ")
|
||||
.AppendLine(" [f_sp] as ProcessSigma ")
|
||||
.AppendLine(" from [spcepiworld].[dbo].[test_dat] test ")
|
||||
.AppendLine(" join [spcepiworld].[dbo].[ctrl_lim] ctrl ")
|
||||
.AppendLine(" on test.f_test = ctrl.f_test ")
|
||||
.AppendLine(" and test.f_tsgp = 1104848523 /* Product Data */ ")
|
||||
.AppendLine(" join [spcepiworld].[dbo].[part_dat] part ")
|
||||
.AppendLine(" on part.f_part = ctrl.f_part ")
|
||||
.AppendLine(" and ctrl.f_test = 1125073605 /* Average Sum of Defects */ ")
|
||||
.AppendLine(" join [spcepiworld].[dbo].[prcs_dat] process ")
|
||||
.AppendLine(" on process.f_prcs = ctrl.f_prcs ")
|
||||
.AppendLine(" where test.f_name = 'Average Sum of Defects' ")
|
||||
.Append(" and process.f_name = '").Append(process).AppendLine("' ")
|
||||
.Append(" and part.f_name = '").Append(part).AppendLine("' ")
|
||||
.AppendLine(" for json path; ");
|
||||
return result.ToString();
|
||||
} // cSpell:enable
|
||||
|
||||
string IInfinityQSV4Repository.GetProductDataAverageSumOfDefectsProcessMeanProcessSigma(string process, string? recipe)
|
||||
{
|
||||
StringBuilder result;
|
||||
if (!string.IsNullOrEmpty(_MockRoot))
|
||||
{
|
||||
string json = File.ReadAllText(Path.Combine(string.Concat(AppContext.BaseDirectory, _MockRoot), $"{_RepositoryName}-{nameof(IInfinityQSV4Repository.GetProductDataAverageSumOfDefectsProcessMeanProcessSigma)}.json"));
|
||||
result = new(json);
|
||||
}
|
||||
else
|
||||
{
|
||||
IInfinityQSV4Repository infinityQSV4Repository = this;
|
||||
string commandText = infinityQSV4Repository.GetCommandText(process, recipe);
|
||||
result = GetForJsonPath(_DBConnectionFactory, commandText, useIqsConnection: false);
|
||||
if (result.Length == 0)
|
||||
result = new("{}");
|
||||
}
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
private JsonElement[] GetAllReactorsAsJsonElementElement()
|
||||
{
|
||||
JsonElement[]? results;
|
||||
HttpClient httpClient = _HttpClientFactory.CreateClient();
|
||||
Task<string> task = httpClient.GetStringAsync($"{_OpenInsightApplicationProgrammingInterface}/reactors");
|
||||
task.Wait();
|
||||
JsonElement? jsonElement = JsonSerializer.Deserialize<JsonElement>(task.Result);
|
||||
if (jsonElement is null)
|
||||
throw new NullReferenceException(nameof(jsonElement));
|
||||
string json = jsonElement.Value.EnumerateObject().First().Value.ToString();
|
||||
results = JsonSerializer.Deserialize<JsonElement[]>(json);
|
||||
if (results is null)
|
||||
throw new NullReferenceException(nameof(results));
|
||||
return results;
|
||||
}
|
||||
|
||||
private ReadOnlyDictionary<int, Reactor> GetReactorsMatchingType(string? reactTypeFilter)
|
||||
{
|
||||
Dictionary<int, Reactor> results = new();
|
||||
string json;
|
||||
Reactor? reactor;
|
||||
JsonElement[]? jsonElements = GetAllReactorsAsJsonElementElement();
|
||||
foreach (JsonElement jsonElement in jsonElements)
|
||||
{
|
||||
json = jsonElement.EnumerateObject().First().Value.ToString();
|
||||
if (reactTypeFilter is not null && !json.Contains(reactTypeFilter))
|
||||
continue;
|
||||
try
|
||||
{ reactor = JsonSerializer.Deserialize(json, ReactorSourceGenerationContext.Default.Reactor); }
|
||||
catch (Exception) { reactor = null; }
|
||||
if (reactor is null || (reactTypeFilter is not null && reactor.ReactType != reactTypeFilter))
|
||||
continue;
|
||||
results.Add(reactor.ReactorNo, reactor);
|
||||
}
|
||||
return new(results);
|
||||
}
|
||||
|
||||
string IInfinityQSV4Repository.GetCommandText(List<string> eppReactorNumbers)
|
||||
{ // cSpell:disable
|
||||
StringBuilder result = new();
|
||||
_ = result
|
||||
.AppendLine(" select se.f_sgrp, ")
|
||||
.AppendLine(" dateadd(HH, -7, (dateadd(SS, convert(bigint, se.f_sgrp), '19700101'))) date_time, ")
|
||||
.AppendLine(" iq.pr_name, ")
|
||||
.AppendLine(" iq.pd_name, ")
|
||||
.AppendLine(" max(case ")
|
||||
.AppendLine(" when td.f_test = 1104769646 ")
|
||||
.AppendLine(" then se.f_val ")
|
||||
.AppendLine(" else null ")
|
||||
.AppendLine(" end) as iq_value, ")
|
||||
.AppendLine(" max(case ")
|
||||
.AppendLine(" when td.f_test = 1312288843 ")
|
||||
.AppendLine(" then se.f_val else null ")
|
||||
.AppendLine(" end) as iq_temp_offset_percent ")
|
||||
.AppendLine(" from ( ")
|
||||
.AppendLine(" select ")
|
||||
.AppendLine(" max(se.f_sgrp) se_max_sgrp, ")
|
||||
.AppendLine(" se.f_test se_test, ")
|
||||
.AppendLine(" pr.f_name pr_name, ")
|
||||
.AppendLine(" pd.f_name pd_name ")
|
||||
.AppendLine(" from [spcepiworld].[dbo].[sgrp_ext] se ")
|
||||
.AppendLine(" join [spcepiworld].[dbo].[prcs_dat] pr ")
|
||||
.AppendLine(" on se.f_prcs = pr.f_prcs ")
|
||||
.AppendLine(" join [spcepiworld].[dbo].[part_dat] pd ")
|
||||
.AppendLine(" on se.f_part = pd.f_part ")
|
||||
.AppendLine(" where se.f_flag = 0 ")
|
||||
.Append(" and pr.f_name in (").Append(string.Join(',', eppReactorNumbers)).AppendLine(") ")
|
||||
.AppendLine(" and pd.f_name = '1090 - Full Load' ")
|
||||
.AppendLine(" and se.f_test in (1104769646, 1312288843) ")
|
||||
.AppendLine(" group by se.f_test, ")
|
||||
.AppendLine(" pr.f_name, ")
|
||||
.AppendLine(" pd.f_name ")
|
||||
.AppendLine(" ) as iq ")
|
||||
.AppendLine(" join [spcepiworld].[dbo].[sgrp_ext] se ")
|
||||
.AppendLine(" on iq.se_max_sgrp = se.f_sgrp ")
|
||||
.AppendLine(" join [spcepiworld].[dbo].[test_dat] td ")
|
||||
.AppendLine(" on iq.se_test = td.f_test ")
|
||||
.AppendLine(" and se.f_test = td.f_test ")
|
||||
.AppendLine(" where se.f_flag = 0 ")
|
||||
.AppendLine(" and td.f_test in (1104769646, 1312288843) ")
|
||||
.AppendLine(" group by se.f_sgrp, ")
|
||||
.AppendLine(" iq.pr_name, ")
|
||||
.AppendLine(" iq.pd_name ")
|
||||
.AppendLine(" order by iq.pr_name ")
|
||||
.AppendLine(" for json path; ");
|
||||
return result.ToString();
|
||||
} // cSpell:enable
|
||||
|
||||
private static List<string> Convert(int[] night)
|
||||
{
|
||||
List<string> results = new();
|
||||
foreach (int reactor in night)
|
||||
results.Add(reactor.ToString());
|
||||
return results;
|
||||
}
|
||||
|
||||
List<string[]> IInfinityQSV4Repository.GetEpiProTempVerificationRows(int[] night)
|
||||
{
|
||||
List<string[]>? results;
|
||||
List<string> eppReactorNumbers = new();
|
||||
List<string> nightSiftReactors = Convert(night);
|
||||
ReadOnlyDictionary<int, Reactor> eppReactors = GetReactorsMatchingType("EPP");
|
||||
foreach (KeyValuePair<int, Reactor> keyValuePair in eppReactors)
|
||||
eppReactorNumbers.Add($"'{keyValuePair.Key}'");
|
||||
if (!string.IsNullOrEmpty(_MockRoot))
|
||||
{
|
||||
string json = File.ReadAllText(Path.Combine(string.Concat(AppContext.BaseDirectory, _MockRoot), $"{_RepositoryName}-{nameof(IInfinityQSV4Repository.GetEpiProTempVerificationRows)}.json"));
|
||||
results = JsonSerializer.Deserialize<List<string[]>>(json);
|
||||
if (results is null)
|
||||
throw new NullReferenceException(nameof(results));
|
||||
}
|
||||
else
|
||||
{
|
||||
bool found;
|
||||
results = new();
|
||||
int loadedCount;
|
||||
Reactor? reactor;
|
||||
string loadedRDS;
|
||||
int reactorNumber;
|
||||
TimeSpan timeSpan;
|
||||
List<string> columns;
|
||||
List<int> used = new();
|
||||
List<int> dayShiftOrder = new();
|
||||
long ticks = DateTime.Now.Ticks;
|
||||
List<int> nightShiftOrder = new();
|
||||
InfinityQS1090FullLoad infinityQS1090FullLoad;
|
||||
IInfinityQSV4Repository infinityQSV4Repository = this;
|
||||
string commandText = infinityQSV4Repository.GetCommandText(eppReactorNumbers);
|
||||
StringBuilder stringBuilder = GetForJsonPath(_DBConnectionFactory, commandText, useIqsConnection: false);
|
||||
InfinityQS1090FullLoad[]? infinityQS1090FullLoads = stringBuilder.Length == 0 ? Array.Empty<InfinityQS1090FullLoad>() : JsonSerializer.Deserialize(stringBuilder.ToString(), InfinityQS1090FullLoadArraySourceGenerationContext.Default.InfinityQS1090FullLoadArray);
|
||||
if (infinityQS1090FullLoads is null)
|
||||
throw new NullReferenceException(nameof(infinityQS1090FullLoads));
|
||||
for (int i = 0; i < infinityQS1090FullLoads.Length; i++)
|
||||
{
|
||||
infinityQS1090FullLoad = infinityQS1090FullLoads[i];
|
||||
if (infinityQS1090FullLoad.Reactor is null)
|
||||
continue;
|
||||
if (!int.TryParse(infinityQS1090FullLoad.Reactor, out reactorNumber))
|
||||
continue;
|
||||
if (!eppReactors.TryGetValue(reactorNumber, out reactor))
|
||||
continue;
|
||||
if (!nightSiftReactors.Contains(infinityQS1090FullLoad.Reactor))
|
||||
dayShiftOrder.Add(i);
|
||||
else
|
||||
nightShiftOrder.Add(i);
|
||||
}
|
||||
for (int i = 0; i < infinityQS1090FullLoads.Length; i++)
|
||||
{
|
||||
if (used.Contains(i) || !dayShiftOrder.Contains(i))
|
||||
continue;
|
||||
infinityQS1090FullLoad = infinityQS1090FullLoads[i];
|
||||
if (infinityQS1090FullLoad.Reactor is null)
|
||||
continue;
|
||||
if (!int.TryParse(infinityQS1090FullLoad.Reactor, out reactorNumber))
|
||||
continue;
|
||||
if (!eppReactors.TryGetValue(reactorNumber, out reactor))
|
||||
continue;
|
||||
used.Add(i);
|
||||
found = false;
|
||||
timeSpan = new(ticks - infinityQS1090FullLoad.SubGroupIdFormatted.Ticks);
|
||||
loadedCount = reactor.LoadedRDS is null ? 0 : reactor.LoadedRDS.Count;
|
||||
loadedRDS = reactor.LoadedRDS is null ? " " : reactor.LoadedRDS[0].ToString();
|
||||
results.Add(new string[]{
|
||||
reactor.ReactorNo.ToString(),
|
||||
reactor.E10State,
|
||||
loadedRDS,
|
||||
infinityQS1090FullLoad.Value.ToString(),
|
||||
infinityQS1090FullLoad.TemperatureOffsetPercentage.ToString(),
|
||||
infinityQS1090FullLoad.SubGroupIdFormatted.ToString(),
|
||||
Math.Floor(timeSpan.TotalHours).ToString()
|
||||
});
|
||||
for (int j = i + 1; j < infinityQS1090FullLoads.Length; j++)
|
||||
{
|
||||
if (used.Contains(j) || !nightShiftOrder.Contains(j))
|
||||
continue;
|
||||
infinityQS1090FullLoad = infinityQS1090FullLoads[j];
|
||||
if (infinityQS1090FullLoad.Reactor is null)
|
||||
continue;
|
||||
if (!int.TryParse(infinityQS1090FullLoad.Reactor, out reactorNumber))
|
||||
continue;
|
||||
if (!eppReactors.TryGetValue(reactorNumber, out reactor))
|
||||
continue;
|
||||
used.Add(j);
|
||||
found = true;
|
||||
timeSpan = new(ticks - infinityQS1090FullLoad.SubGroupIdFormatted.Ticks);
|
||||
loadedCount = reactor.LoadedRDS is null ? 0 : reactor.LoadedRDS.Count;
|
||||
loadedRDS = reactor.LoadedRDS is null ? " " : reactor.LoadedRDS[0].ToString();
|
||||
columns = new();
|
||||
columns.AddRange(results[^1]);
|
||||
columns.AddRange(new string[]
|
||||
{
|
||||
" ",
|
||||
reactor.ReactorNo.ToString(),
|
||||
reactor.E10State,
|
||||
loadedRDS,
|
||||
infinityQS1090FullLoad.Value.ToString(),
|
||||
infinityQS1090FullLoad.TemperatureOffsetPercentage.ToString(),
|
||||
infinityQS1090FullLoad.SubGroupIdFormatted.ToString(),
|
||||
Math.Floor(timeSpan.TotalHours).ToString()
|
||||
});
|
||||
results[^1] = columns.ToArray();
|
||||
break;
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
columns = new();
|
||||
columns.AddRange(results[^1]);
|
||||
columns.Add(" ");
|
||||
columns.Add(" ");
|
||||
columns.Add(" ");
|
||||
columns.Add(" ");
|
||||
columns.Add(" ");
|
||||
columns.Add(" ");
|
||||
columns.Add(" ");
|
||||
columns.Add(" ");
|
||||
results[^1] = columns.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
string IInfinityQSV4Repository.GetEpiProTempVerification(int[] night)
|
||||
{
|
||||
StringBuilder result = new();
|
||||
IInfinityQSV4Repository infinityQSV4Repository = this;
|
||||
List<string[]> collection = infinityQSV4Repository.GetEpiProTempVerificationRows(night);
|
||||
foreach (string[] row in collection)
|
||||
{
|
||||
_ = result.Append("<tr>");
|
||||
foreach (string column in row)
|
||||
_ = result.Append("<td>").Append(column).Append("</td>");
|
||||
_ = result.Append("</tr>");
|
||||
}
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
List<Reactor> IInfinityQSV4Repository.GetReactors()
|
||||
{
|
||||
List<Reactor> results = new();
|
||||
ReadOnlyDictionary<int, Reactor> keyValuePairs = GetReactorsMatchingType(reactTypeFilter: null);
|
||||
foreach (KeyValuePair<int, Reactor> keyValuePair in keyValuePairs)
|
||||
results.Add(keyValuePair.Value);
|
||||
return results;
|
||||
}
|
||||
|
||||
string IInfinityQSV4Repository.GetRunDataSheet(string rds)
|
||||
{
|
||||
string result;
|
||||
HttpClient httpClient = _HttpClientFactory.CreateClient();
|
||||
Task<string> task = httpClient.GetStringAsync($"{_OpenInsightApplicationProgrammingInterface}/materials/rds/{rds}");
|
||||
task.Wait();
|
||||
result = task.Result;
|
||||
if (rds == "123456")
|
||||
{
|
||||
RunDataSheetRoot? runDataSheetRoot = JsonSerializer.Deserialize(result, RunDataSheetRootSourceGenerationContext.Default.RunDataSheetRoot);
|
||||
if (runDataSheetRoot is not null)
|
||||
{
|
||||
List<string> lines = new();
|
||||
foreach (char ch in runDataSheetRoot.RunDataSheet.PSN)
|
||||
{ }
|
||||
result = string.Join(Environment.NewLine, lines);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
string IInfinityQSV4Repository.GetProductionSpecification(string part)
|
||||
{
|
||||
string result;
|
||||
HttpClient httpClient = _HttpClientFactory.CreateClient();
|
||||
Task<string> task = httpClient.GetStringAsync($"{_OpenInsightApplicationProgrammingInterface}/materials/prod_spec/{part}");
|
||||
task.Wait();
|
||||
result = task.Result;
|
||||
if (part == "4992")
|
||||
{
|
||||
ProdSpecRoot? prodSpecRoot = JsonSerializer.Deserialize(result, ProdSpecRootSourceGenerationContext.Default.ProdSpecRoot);
|
||||
if (prodSpecRoot is not null)
|
||||
{
|
||||
List<string> lines = new();
|
||||
foreach (PrsStage prsStage in prodSpecRoot.ProdSpec.PrsStages)
|
||||
{
|
||||
if (prsStage.QaMetTests is null)
|
||||
continue;
|
||||
foreach (QaMetTest qaMetTest in prsStage.QaMetTests)
|
||||
{
|
||||
lines.Add(qaMetTest.Test);
|
||||
// lines.Add(qaMetTest.Slots);
|
||||
lines.Add(qaMetTest.Recipe);
|
||||
lines.Add(qaMetTest.RecipePattern);
|
||||
if (qaMetTest.Test != "THICK_ONLY")
|
||||
continue;
|
||||
lines.Add(qaMetTest.Min.ToString());
|
||||
lines.Add(qaMetTest.Max.ToString());
|
||||
}
|
||||
}
|
||||
result = string.Join(Environment.NewLine, lines);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void GetOI(InfinityQSV4 infinityQSV4)
|
||||
{
|
||||
IInfinityQSV4Repository infinityQSV4Repository = this;
|
||||
if (string.IsNullOrEmpty(infinityQSV4.Part))
|
||||
throw new ArgumentException(nameof(infinityQSV4.Part));
|
||||
string json = infinityQSV4Repository.GetProductionSpecification(infinityQSV4.Part);
|
||||
ProdSpecRoot? prodSpec = JsonSerializer.Deserialize(json, ProdSpecRootSourceGenerationContext.Default.ProdSpecRoot);
|
||||
if (prodSpec is null)
|
||||
{
|
||||
if (prodSpec is null)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
|
||||
private static Record? GetValue(AppSettings appSettings, string line)
|
||||
{
|
||||
Record? result;
|
||||
string[] attributes = line.Split('>');
|
||||
if (attributes.Length != 3)
|
||||
result = null;
|
||||
else
|
||||
{
|
||||
string[] text = attributes[1].Replace(appSettings.IqsRed, "red").Replace(appSettings.IqsYellow, "Yellow").Split($"</{line[1]}{line[2]}");
|
||||
if (text.Length != 2)
|
||||
result = null;
|
||||
else
|
||||
{
|
||||
string[] attributeValues = attributes[0].Split('"');
|
||||
if (attributeValues.Length != 3)
|
||||
result = new(null, text[0]);
|
||||
else
|
||||
{
|
||||
result = new(attributeValues[1], text[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<Record> GetRecords(AppSettings appSettings, string file)
|
||||
{
|
||||
Record? record;
|
||||
List<Record> results = new();
|
||||
List<string> checkColumns = new();
|
||||
string[] lines = File.ReadAllLines(file);
|
||||
foreach (string line in lines)
|
||||
{
|
||||
if (line.StartsWith("<th"))
|
||||
{
|
||||
record = GetValue(appSettings, line);
|
||||
if (record is null)
|
||||
continue;
|
||||
checkColumns.Add(record.Text);
|
||||
}
|
||||
if (line.StartsWith("<td"))
|
||||
{
|
||||
record = GetValue(appSettings, line);
|
||||
if (record is null)
|
||||
continue;
|
||||
results.Add(record);
|
||||
}
|
||||
}
|
||||
if (string.Join(',', checkColumns) != appSettings.IqsColumns)
|
||||
throw new NotSupportedException("Columns don't match!");
|
||||
return new(results);
|
||||
}
|
||||
|
||||
private static List<Dictionary<string, Record>> GetCollection(string columns, ReadOnlyCollection<Record> records)
|
||||
{
|
||||
string[] columnNames = columns.Split(',');
|
||||
List<Dictionary<string, Record>> collection = new();
|
||||
Dictionary<string, Record> keyValuePairs;
|
||||
for (int i = 0; i < records.Count; i++)
|
||||
{
|
||||
keyValuePairs = new();
|
||||
for (int j = 0; j < columnNames.Length; j++)
|
||||
{
|
||||
keyValuePairs.Add(columnNames[j], records[i]);
|
||||
i++;
|
||||
}
|
||||
i--;
|
||||
collection.Add(keyValuePairs);
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
private static Dictionary<string, List<string>> GetResults(AppSettings appSettings, List<Dictionary<string, Record>> collection)
|
||||
{
|
||||
Dictionary<string, List<string>> results = new();
|
||||
Record record;
|
||||
List<string>? colors;
|
||||
foreach (Dictionary<string, Record> keyValuePairs in collection)
|
||||
{
|
||||
record = keyValuePairs[appSettings.IqsKey];
|
||||
if (!results.TryGetValue(record.Text, out colors))
|
||||
{
|
||||
results.Add(record.Text, new());
|
||||
if (!results.TryGetValue(record.Text, out colors))
|
||||
throw new Exception();
|
||||
}
|
||||
if (record.Color is null)
|
||||
continue;
|
||||
colors.Add(record.Color.Replace(appSettings.IqsRed, "red").Replace(appSettings.IqsYellow, "Yellow"));
|
||||
}
|
||||
foreach (KeyValuePair<string, List<string>> keyValuePair in results)
|
||||
keyValuePair.Value.Sort();
|
||||
return results;
|
||||
}
|
||||
|
||||
private static void FileFindReplaceAndSave(AppSettings appSettings, string iqsFile, FileInfo fileInfo)
|
||||
{
|
||||
string lines = File.ReadAllText(iqsFile).Replace(appSettings.IqsRed, "red").Replace(appSettings.IqsYellow, "Yellow");
|
||||
File.WriteAllText(fileInfo.FullName, lines);
|
||||
}
|
||||
|
||||
Dictionary<string, List<string>> IInfinityQSV4Repository.GetEngineeringSpcReview()
|
||||
{
|
||||
Dictionary<string, List<string>>? results;
|
||||
if (!string.IsNullOrEmpty(_MockRoot))
|
||||
{
|
||||
string json = File.ReadAllText(Path.Combine(string.Concat(AppContext.BaseDirectory, _MockRoot), $"{_RepositoryName}-{nameof(IInfinityQSV4Repository.GetEngineeringSpcReview)}.json"));
|
||||
results = JsonSerializer.Deserialize<Dictionary<string, List<string>>>(json);
|
||||
if (results is null)
|
||||
throw new NullReferenceException(nameof(results));
|
||||
}
|
||||
else
|
||||
{
|
||||
FileInfo iqsFileInfo = new(_AppSettings.IqsFile);
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
string? assemblyName = assembly.GetName()?.Name;
|
||||
if (string.IsNullOrEmpty(assemblyName))
|
||||
throw new Exception();
|
||||
FileInfo localFileInfo = new(Path.Combine(AppContext.BaseDirectory, "wwwroot", iqsFileInfo.Name));
|
||||
if (!localFileInfo.Exists && (!iqsFileInfo.Exists || iqsFileInfo.Length == 0))
|
||||
results = new();
|
||||
else
|
||||
{
|
||||
if (!localFileInfo.Exists || localFileInfo.LastWriteTime != iqsFileInfo.LastWriteTime)
|
||||
FileFindReplaceAndSave(_AppSettings, iqsFileInfo.FullName, localFileInfo);
|
||||
ReadOnlyCollection<Record> records = !iqsFileInfo.Exists || iqsFileInfo.Length == 0 ? GetRecords(_AppSettings, localFileInfo.FullName) : GetRecords(_AppSettings, iqsFileInfo.FullName);
|
||||
List<Dictionary<string, Record>> collection = GetCollection(_AppSettings.IqsColumns, records);
|
||||
results = GetResults(_AppSettings, collection);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
@ -312,7 +312,7 @@ public class MetrologyRepository : IMetrologyRepository
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
internal DataTable GetHeaders(int toolTypeId, DateTime? startTime, DateTime? endTime, int? pageNo, int? pageSize, long? headerId, out long totalRecords)
|
||||
internal DataTable GetHeaders(int toolTypeId, string? startTime, string? endTime, int? pageNo, int? pageSize, long? headerId, out long totalRecords)
|
||||
{
|
||||
ToolType tt = GetToolTypeByID(toolTypeId) ?? throw new Exception("Invalid tool type ID");
|
||||
|
||||
@ -341,16 +341,16 @@ public class MetrologyRepository : IMetrologyRepository
|
||||
}
|
||||
else
|
||||
{
|
||||
if (startTime.HasValue)
|
||||
if (startTime is not null)
|
||||
{
|
||||
whereClause = "[Date] >= @StartTime ";
|
||||
DateTime startTimeLocal = startTime.Value.ToLocalTime();
|
||||
AddParameter(cmd, "@StartTime", startTimeLocal);
|
||||
DateTime startTimeLocal = DateTime.Parse(startTime).ToLocalTime();
|
||||
AddParameter(cmd, "@StartTime", startTimeLocal.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
if (endTime.HasValue)
|
||||
if (endTime is not null)
|
||||
{
|
||||
DateTime endTimeLocal = endTime.Value.ToLocalTime();
|
||||
DateTime endTimeLocal = DateTime.Parse(endTime).ToLocalTime();
|
||||
TimeSpan timeSpan = new(DateTime.Now.Ticks - endTimeLocal.Ticks);
|
||||
if (timeSpan.TotalMinutes > 5)
|
||||
{
|
||||
@ -358,7 +358,7 @@ public class MetrologyRepository : IMetrologyRepository
|
||||
whereClause += "AND ";
|
||||
whereClause += "[Date] <= @EndTime ";
|
||||
|
||||
AddParameter(cmd, "@EndTime", endTimeLocal);
|
||||
AddParameter(cmd, "@EndTime", endTimeLocal.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -773,7 +773,7 @@ public class MetrologyRepository : IMetrologyRepository
|
||||
void IMetrologyRepository.PurgeExistingData(int toolTypeId, string title) => PurgeExistingData(toolTypeId, title);
|
||||
long IMetrologyRepository.InsertToolDataJSON(JToken jsonbody, long headerId, List<ToolTypeMetadata> metaData, string tableName) => InsertToolDataJSON(jsonbody, headerId, metaData, tableName);
|
||||
DataTable IMetrologyRepository.ExportData(string spName, DateTime startTime, DateTime endTime) => ExportData(spName, startTime, endTime);
|
||||
DataTable IMetrologyRepository.GetHeaders(int toolTypeId, DateTime? startTime, DateTime? endTime, int? pageNo, int? pageSize, long? headerid, out long totalRecords) => GetHeaders(toolTypeId, startTime, endTime, pageNo, pageSize, headerid, out totalRecords);
|
||||
DataTable IMetrologyRepository.GetHeaders(int toolTypeId, string? startTime, string? endTime, int? pageNo, int? pageSize, long? headerid, out long totalRecords) => GetHeaders(toolTypeId, startTime, endTime, pageNo, pageSize, headerid, out totalRecords);
|
||||
DataTable IMetrologyRepository.GetData(int toolTypeId, long headerId) => GetData(toolTypeId, headerId);
|
||||
HeaderCommon[] IMetrologyRepository.GetHeaderTitles(int? toolTypeId, int? pageNo, int? pageSize, out long totalRecords) => GetHeaderTitles(toolTypeId, pageNo, pageSize, out totalRecords);
|
||||
Guid IMetrologyRepository.GetHeaderAttachmentIDByTitle(int toolTypeId, string title) => GetHeaderAttachmentIDByTitle(toolTypeId, title);
|
||||
|
@ -78,7 +78,7 @@ public class ToolTypesRepository : IToolTypesRepository
|
||||
|
||||
// Gets headers, request/response format is to allow paging with igniteUI
|
||||
// The headerid parameter is used for navigating directly to a header, when the button is clicked in Awaiting Dispo
|
||||
Result<DataTable> IToolTypesRepository.GetHeaders(IMetrologyRepository metrologyRepository, int id, DateTime? datebegin, DateTime? dateend, int? page, int? pagesize, long? headerid)
|
||||
Result<DataTable> IToolTypesRepository.GetHeaders(IMetrologyRepository metrologyRepository, int id, string? datebegin, string? dateend, int? page, int? pagesize, long? headerid)
|
||||
{
|
||||
Result<DataTable>? r;
|
||||
if (!string.IsNullOrEmpty(_MockRoot))
|
||||
@ -229,7 +229,7 @@ public class ToolTypesRepository : IToolTypesRepository
|
||||
return result;
|
||||
}
|
||||
|
||||
Result<DataTable> IToolTypesRepository.GetExportData(IMetrologyRepository metrologyRepository, int toolTypeId, DateTime? datebegin, DateTime? dateend)
|
||||
Result<DataTable> IToolTypesRepository.GetExportData(IMetrologyRepository metrologyRepository, int toolTypeId, string? datebegin, string? dateend)
|
||||
{
|
||||
Result<DataTable>? r;
|
||||
if (!string.IsNullOrEmpty(_MockRoot))
|
||||
@ -241,12 +241,12 @@ public class ToolTypesRepository : IToolTypesRepository
|
||||
}
|
||||
else
|
||||
{
|
||||
dateend ??= DateTime.Now;
|
||||
datebegin ??= dateend.Value.AddMonths(-1);
|
||||
DateTime dateEnd = dateend is null ? DateTime.Now : DateTime.Parse(dateend);
|
||||
DateTime dateBegin = datebegin is null ? dateEnd.AddMonths(-1) : DateTime.Parse(datebegin);
|
||||
ToolType tt = metrologyRepository.GetToolTypeByID(toolTypeId);
|
||||
if (string.IsNullOrEmpty(tt.ExportSPName))
|
||||
throw new NullReferenceException(nameof(tt.ExportSPName));
|
||||
DataTable dataTable = metrologyRepository.ExportData(tt.ExportSPName, datebegin.Value, dateend.Value);
|
||||
DataTable dataTable = metrologyRepository.ExportData(tt.ExportSPName, dateBegin, dateEnd);
|
||||
r = new()
|
||||
{
|
||||
Results = dataTable,
|
||||
@ -305,7 +305,7 @@ public class ToolTypesRepository : IToolTypesRepository
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
byte[] IToolTypesRepository.GetCSVExport(IMetrologyRepository metrologyRepository, int toolTypeId, DateTime? datebegin, DateTime? dateend)
|
||||
byte[] IToolTypesRepository.GetCSVExport(IMetrologyRepository metrologyRepository, int toolTypeId, string? datebegin, string? dateend)
|
||||
{
|
||||
byte[] results;
|
||||
Result<DataTable> result;
|
||||
|
228
Server/Repositories/WaferCounterRepository.cs
Normal file
228
Server/Repositories/WaferCounterRepository.cs
Normal file
@ -0,0 +1,228 @@
|
||||
using OI.Metrology.Server.Models;
|
||||
using OI.Metrology.Shared.DataModels;
|
||||
using OI.Metrology.Shared.Models.Stateless;
|
||||
using OI.Metrology.Shared.Repositories;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace OI.Metrology.Server.Repository;
|
||||
|
||||
public class WaferCounterRepository : IWaferCounterRepository
|
||||
{
|
||||
|
||||
private record Record(int Check,
|
||||
int Total,
|
||||
string? SlotMap);
|
||||
|
||||
private readonly string _MockRoot;
|
||||
private readonly string _RepositoryName;
|
||||
private readonly AppSettings _AppSettings;
|
||||
private readonly IHttpClientFactory _HttpClientFactory;
|
||||
private readonly IDbConnectionFactory _DBConnectionFactory;
|
||||
private readonly string _OpenInsightApplicationProgrammingInterface;
|
||||
|
||||
public WaferCounterRepository(AppSettings appSettings, IDbConnectionFactory dbConnectionFactory, IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_AppSettings = appSettings;
|
||||
_MockRoot = appSettings.MockRoot;
|
||||
_HttpClientFactory = httpClientFactory;
|
||||
_DBConnectionFactory = dbConnectionFactory;
|
||||
_RepositoryName = nameof(WaferCounterRepository)[..^10];
|
||||
_OpenInsightApplicationProgrammingInterface = appSettings.OpenInsightApplicationProgrammingInterface;
|
||||
}
|
||||
|
||||
private static void MoveFile(string waferSizeDirectory, FileInfo fileInfo)
|
||||
{
|
||||
Calendar calendar = new CultureInfo("en-US").Calendar;
|
||||
string weekOfYear = $"{fileInfo.LastWriteTime:yyyy}_Week_{calendar.GetWeekOfYear(fileInfo.LastWriteTime, CalendarWeekRule.FirstDay, DayOfWeek.Sunday):00}";
|
||||
string checkDirectory = Path.Combine(waferSizeDirectory, "Archive", weekOfYear);
|
||||
if (!Directory.Exists(checkDirectory))
|
||||
_ = Directory.CreateDirectory(checkDirectory);
|
||||
string checkFile = Path.Combine(checkDirectory, fileInfo.Name);
|
||||
if (!File.Exists(checkFile))
|
||||
File.Move(fileInfo.FullName, checkFile);
|
||||
}
|
||||
|
||||
private static Record GetRecord(string line1, string line2)
|
||||
{
|
||||
Record result;
|
||||
string? waferMap = string.IsNullOrEmpty(line2) || line2.Length != 8 ? null : line2.Substring(1, 1);
|
||||
int check = waferMap == "1" ? 1 : 0;
|
||||
int total = int.Parse(line1[1..]);
|
||||
// string wafers = Array.from(line2[2..]);
|
||||
foreach (char item in line2[2..])
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case '0':
|
||||
check += 0;
|
||||
waferMap += "0000";
|
||||
break;
|
||||
case '1':
|
||||
check += 1;
|
||||
waferMap += "0001";
|
||||
break;
|
||||
case '2':
|
||||
check += 1;
|
||||
waferMap += "0010";
|
||||
break;
|
||||
case '3':
|
||||
check += 2;
|
||||
waferMap += "0011";
|
||||
break;
|
||||
case '4':
|
||||
check += 1;
|
||||
waferMap += "0100";
|
||||
break;
|
||||
case '5':
|
||||
check += 2;
|
||||
waferMap += "0101";
|
||||
break;
|
||||
case '6':
|
||||
check += 2;
|
||||
waferMap += "0110";
|
||||
break;
|
||||
case '7':
|
||||
check += 3;
|
||||
waferMap += "0111";
|
||||
break;
|
||||
case '8':
|
||||
check += 1;
|
||||
waferMap += "1000";
|
||||
break;
|
||||
case '9':
|
||||
check += 2;
|
||||
waferMap += "1001";
|
||||
break;
|
||||
case 'A':
|
||||
check += 2;
|
||||
waferMap += "1010";
|
||||
break;
|
||||
case 'B':
|
||||
check += 3;
|
||||
waferMap += "1011";
|
||||
break;
|
||||
case 'C':
|
||||
check += 2;
|
||||
waferMap += "1100";
|
||||
break;
|
||||
case 'D':
|
||||
check += 3;
|
||||
waferMap += "1101";
|
||||
break;
|
||||
case 'E':
|
||||
check += 3;
|
||||
waferMap += "1110";
|
||||
break;
|
||||
case 'F':
|
||||
check += 4;
|
||||
waferMap += "1111";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
result = new(check, total, waferMap);
|
||||
return result;
|
||||
}
|
||||
|
||||
private string GetWaferSizeDirectory(string area, string waferSize, bool destination) =>
|
||||
destination ? Path.Combine(_AppSettings.WaferCounterDestinationDirectory, area, waferSize) : Path.Combine(_AppSettings.WaferCounterRootDirectory, area, waferSize);
|
||||
|
||||
string? IWaferCounterRepository.GetSlotMap(string line1, string line2) =>
|
||||
GetRecord(line1, line2).SlotMap;
|
||||
|
||||
private static FileInfo[] GetFileInfoCollection(string waferSizeDirectory)
|
||||
{
|
||||
List<FileInfo> results = new();
|
||||
FileInfo[] fileInfoCollection;
|
||||
string[] files = !Directory.Exists(waferSizeDirectory) ? Array.Empty<string>() : Directory.GetFiles(waferSizeDirectory, "*.wc", SearchOption.TopDirectoryOnly);
|
||||
fileInfoCollection = (from l in files select new FileInfo(l)).OrderByDescending(l => l.LastWriteTime).ToArray();
|
||||
if (fileInfoCollection.Length > 0)
|
||||
results.Add(fileInfoCollection[0]);
|
||||
|
||||
for (int i = 1; i < fileInfoCollection.Length; i++)
|
||||
MoveFile(waferSizeDirectory, fileInfoCollection[i]);
|
||||
return fileInfoCollection;
|
||||
}
|
||||
|
||||
private static WaferCounter? GetLastQuantityAndSlotMapWithText(string waferSize, string text, FileInfo fileInfo)
|
||||
{
|
||||
WaferCounter? result;
|
||||
string[] lines = File.ReadAllLines(fileInfo.FullName);
|
||||
if (lines.Length < 2)
|
||||
result = null;
|
||||
else
|
||||
{
|
||||
string[] segments = fileInfo.Name.Split('-');
|
||||
Record record = GetRecord(lines[0], lines[1]);
|
||||
string equipmentId = segments.Length < 2 ? fileInfo.Name : segments[1].Split('.')[0];
|
||||
if (string.IsNullOrEmpty(record.SlotMap) || record.SlotMap.Length != 25)
|
||||
result = null; // Wrong length!
|
||||
else if (record.Total != record.Check)
|
||||
result = null; // Invalid!
|
||||
else
|
||||
result = new(fileInfo.LastWriteTime, fileInfo.LastWriteTime.ToString("yyyy-MM-dd hh:mm tt"), $"WC{waferSize}{equipmentId}", text, record.Total, record.SlotMap);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
WaferCounter? IWaferCounterRepository.GetLastQuantityAndSlotMap(string area, string waferSize)
|
||||
{
|
||||
WaferCounter? result;
|
||||
if (!string.IsNullOrEmpty(_MockRoot))
|
||||
{
|
||||
string json = File.ReadAllText(Path.Combine(string.Concat(AppContext.BaseDirectory, _MockRoot), $"{_RepositoryName}-{nameof(IWaferCounterRepository.GetLastQuantityAndSlotMap)}.json"));
|
||||
result = JsonSerializer.Deserialize<WaferCounter>(json);
|
||||
if (result is null)
|
||||
throw new NullReferenceException(nameof(result));
|
||||
}
|
||||
else
|
||||
{
|
||||
string waferSizeDirectory = GetWaferSizeDirectory(area, waferSize, destination: false);
|
||||
FileInfo[] fileInfoCollection = GetFileInfoCollection(waferSizeDirectory);
|
||||
if (fileInfoCollection.Length == 0)
|
||||
result = null;
|
||||
else
|
||||
{
|
||||
string text = string.Empty;
|
||||
result = GetLastQuantityAndSlotMapWithText(waferSize, text, fileInfoCollection[0]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void Save(string waferSizeDestinationDirectory, string area, string waferSize, string text, FileInfo fileInfo, WaferCounter result) =>
|
||||
File.WriteAllText(Path.Combine(waferSizeDestinationDirectory, $"{fileInfo.Name}.csv"), $"100,{waferSize},{area},{fileInfo.LastWriteTime},{text},{result.Total:00},{result.SlotMap} ");
|
||||
|
||||
WaferCounter? IWaferCounterRepository.GetLastQuantityAndSlotMapWithText(string area, string waferSize, string text)
|
||||
{
|
||||
WaferCounter? result;
|
||||
if (!string.IsNullOrEmpty(_MockRoot))
|
||||
{
|
||||
string json = File.ReadAllText(Path.Combine(string.Concat(AppContext.BaseDirectory, _MockRoot), $"{_RepositoryName}-{nameof(IWaferCounterRepository.GetLastQuantityAndSlotMapWithText)}.json"));
|
||||
result = JsonSerializer.Deserialize<WaferCounter>(json);
|
||||
if (result is null)
|
||||
throw new NullReferenceException(nameof(result));
|
||||
}
|
||||
else
|
||||
{
|
||||
string waferSizeDirectory = GetWaferSizeDirectory(area, waferSize, destination: false);
|
||||
FileInfo[] fileInfoCollection = GetFileInfoCollection(waferSizeDirectory);
|
||||
if (fileInfoCollection.Length == 0)
|
||||
result = null;
|
||||
else
|
||||
{
|
||||
result = GetLastQuantityAndSlotMapWithText(waferSize, text, fileInfoCollection[0]);
|
||||
if (result is not null)
|
||||
{
|
||||
string waferSizeDestinationDirectory = _AppSettings.WaferCounterDestinationDirectory;
|
||||
// string waferSizeDestinationDirectory = GetWaferSizeDirectory(area, waferSize, destination: true);
|
||||
Save(waferSizeDestinationDirectory, area, waferSize, text, fileInfoCollection[0], result);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user