mirror of
https://gitlab.cern.ch/wotsubo/PSBoardDataBase.git
synced 2025-06-08 05:55:42 +09:00
289 lines
9.5 KiB
Julia
289 lines
9.5 KiB
Julia
"""
|
|
Module to check PSBoard is dispatchable.
|
|
|
|
Use [`interactive_dispatch_checker`](@ref) for interactive use in QAQC.
|
|
"""
|
|
module DispatchChecker
|
|
|
|
using SQLite
|
|
using DBInterface
|
|
using DataFrames
|
|
using Printf
|
|
using REPL.TerminalMenus
|
|
|
|
export DbConnection
|
|
export is_dispatchable
|
|
|
|
"""
|
|
Stores connection to database.
|
|
|
|
DbConnection(db::SQLite.DB)
|
|
|
|
Constructor.
|
|
"""
|
|
mutable struct DbConnection
|
|
db::SQLite.DB
|
|
df_single_result::DataFrame
|
|
df_extra_results::DataFrame
|
|
df_runs::DataFrame
|
|
function DbConnection(db::SQLite.DB)
|
|
df_single_results =
|
|
DBInterface.execute(db, sql"select * from qaqc_single_run_results") |> DataFrame
|
|
df_extra_results =
|
|
DBInterface.execute(db, sql"select * from qaqc_extra_run_results") |> DataFrame
|
|
df_runs = DBInterface.execute(db, sql"select * from qaqc_runs") |> DataFrame
|
|
new(db, df_single_results, df_extra_results, df_runs)
|
|
end
|
|
end
|
|
|
|
const THRESHOLD_INSUFFICIENT_RESET_WITH_10_CAMPAIGN_1to5 = 0.1
|
|
const THRESHOLD_INSUFFICIENT_RESET_WITH_10_CAMPAIGN_6 = 0.05
|
|
const THRESHOLD_RESET_FAILED_THOUGH_RECONFIG_DONE = 0.1
|
|
const THRESHOLD_ALWAYS_HIT_FLAG_TRUE = 0.1
|
|
const THRESHOLD_BCID_FAIL = 0.05
|
|
|
|
"""
|
|
is_dispatchable(conn::DbConnection, psbid::Int64)
|
|
|
|
Test whether the PS Board with `psbid` is dispatchable from QAQC results in `conn`.
|
|
`conn` is type of [`DbConnection`](@ref).
|
|
Since the current implemented logic is somewhat simple, it returns `missing` if it cannot be decided.
|
|
"""
|
|
function is_dispatchable(conn::DbConnection, psbid::Int64)
|
|
single_results = filter(:psboard_id => ==(psbid), conn.df_single_result)
|
|
extra_results = filter(:psboard_id => ==(psbid), conn.df_extra_results)
|
|
|
|
is_single_passed::Bool =
|
|
nrow(single_results) == 1 && let
|
|
single_result = Tables.rowtable(single_results) |> first
|
|
|
|
# Clock test update was wrong
|
|
# manually assign to psboards whose clock test failed
|
|
if 221 <= single_result.runid < 234
|
|
if single_result.psboard_id == 915
|
|
false
|
|
elseif single_result.psboard_id in [860, 889, 876, 892]
|
|
# though 860 has 2+ rows
|
|
true
|
|
end
|
|
false
|
|
else
|
|
single_result.resistance_test_passed == 1 &&
|
|
single_result.qspip == 1 &&
|
|
single_result.recov == 1 &&
|
|
single_result.power == 1 &&
|
|
single_result.clock == 1
|
|
end
|
|
end
|
|
@debug "" is_single_passed single_results single_results.note
|
|
is_extra_passed::Bool =
|
|
nrow(extra_results) == 1 && let
|
|
extra_result = Tables.rowtable(extra_results) |> first
|
|
f1 =
|
|
!ismissing(extra_result.insufficient_reset_with_10) && begin
|
|
campaign_id =
|
|
filter(:id => ==(extra_result.runid), conn.df_runs).campaign_id
|
|
@assert length(campaign_id) == 1
|
|
campaign_id = first(campaign_id)
|
|
if campaign_id ≤ 5
|
|
extra_result.insufficient_reset_with_10 >=
|
|
extra_result.num_tests *
|
|
THRESHOLD_INSUFFICIENT_RESET_WITH_10_CAMPAIGN_1to5
|
|
else
|
|
extra_result.insufficient_reset_with_10 >=
|
|
extra_result.num_tests *
|
|
THRESHOLD_INSUFFICIENT_RESET_WITH_10_CAMPAIGN_6
|
|
end
|
|
end
|
|
f2 =
|
|
!ismissing(extra_result.reset_failed_though_reconfig_done) &&
|
|
extra_result.reset_failed_though_reconfig_done >=
|
|
extra_result.num_tests * THRESHOLD_RESET_FAILED_THOUGH_RECONFIG_DONE
|
|
f3 =
|
|
!ismissing(extra_result.always_hit_flag_true) &&
|
|
extra_result.always_hit_flag_true >=
|
|
extra_result.num_tests * THRESHOLD_ALWAYS_HIT_FLAG_TRUE
|
|
f4 =
|
|
!ismissing(extra_result.bcid_fail) &&
|
|
extra_result.bcid_fail >= extra_result.num_tests * THRESHOLD_BCID_FAIL
|
|
@debug "" extra_result extra_result.note f1 f2 f3 f4
|
|
!(f1 || f2 || f3 || f4)
|
|
end
|
|
@debug "" is_extra_passed extra_results
|
|
|
|
if is_single_passed & is_extra_passed
|
|
return true
|
|
end
|
|
|
|
# TODO: not yet implemented
|
|
@info "results" sort(single_results, :runid) sort(
|
|
select(
|
|
extra_results,
|
|
Not(
|
|
:id,
|
|
:num_tests,
|
|
:dac_is_0,
|
|
:bcid_fail_111,
|
|
:bcid_fail_000,
|
|
:low_efficiency,
|
|
),
|
|
),
|
|
:runid,
|
|
)
|
|
@debug "results(full)" extra_results
|
|
return missing
|
|
end
|
|
|
|
"""
|
|
Interactive session for QAQC to check PSBoard is ready for dispatch.
|
|
"""
|
|
function interactive_dispatch_checker end
|
|
|
|
"""
|
|
interactive_dispatch_checker(conn::DbConnection)
|
|
"""
|
|
function interactive_dispatch_checker(conn::DbConnection)
|
|
dispatch_list = Int64[]
|
|
|
|
println("Type \"quit\" to exit")
|
|
for _ in 1:1000
|
|
printstyled("PSBoard ID: ", bold = true)
|
|
psbid = let
|
|
rawin = readline()
|
|
if lowercase(rawin) == "quit"
|
|
printstyled("Quit\n", italic = true)
|
|
println()
|
|
break
|
|
end
|
|
m = match(r"^PS(\d+)", rawin)
|
|
if isnothing(m)
|
|
printstyled("Invalid input\n", color = :red)
|
|
continue
|
|
end
|
|
parse(Int64, m[1])
|
|
end
|
|
isdispatchable = is_dispatchable(conn, psbid)
|
|
if ismissing(isdispatchable)
|
|
printstyled("Please determine [y/n]: ", underline = true, color = :cyan)
|
|
isdispatchable = let
|
|
rawin = readline()
|
|
@info "" rawin
|
|
if rawin == "y" || rawin == "Y"
|
|
true
|
|
elseif rawin == "n" || rawin == "N"
|
|
false
|
|
else
|
|
@warn "Invalid input falling back to \"no\""
|
|
false
|
|
end
|
|
end
|
|
end
|
|
if isdispatchable
|
|
printstyled("Ok\n", bold = true, color = :green)
|
|
if psbid in dispatch_list
|
|
println("PSBoard ID $(psbid) is already in dispatch list")
|
|
else
|
|
push!(dispatch_list, psbid)
|
|
println("Added to dispatch list")
|
|
end
|
|
else
|
|
printstyled("No\n", bold = true, color = :red)
|
|
end
|
|
end
|
|
|
|
printstyled("Finished\n")
|
|
|
|
map(dispatch_list) do psbid
|
|
@sprintf "PS%06d" psbid
|
|
end |> (v -> join(v, "\n")) |> print
|
|
println()
|
|
|
|
printstyled("Paste the result to google sheets\n", underline = true)
|
|
@info "Tips: You can use `join(ans, \"\\n\") |> clipboard` in REPL to copy the result to the clipboard"
|
|
|
|
return dispatch_list
|
|
end
|
|
|
|
"""
|
|
interactive_dispatch_checker(database_file::AbstractString)
|
|
|
|
Interactive session for QAQC to check provided PSBoard is ready to dispatch.
|
|
"""
|
|
function interactive_dispatch_checker(database_file::AbstractString)
|
|
conn = DbConnection(SQLite.DB(database_file))
|
|
interactive_dispatch_checker(conn)
|
|
end
|
|
|
|
"""
|
|
scan_dispatchcheck(conn::DbConnection; reasons = NamedTuple[])
|
|
|
|
Interactively scan PS Boards to check they passed the QAQC.
|
|
If the board have suspicious results but actually passed the test, you can select one reason for that board or
|
|
add new reason to explain.
|
|
|
|
# Arguments
|
|
- `reasons::Vector{NamedTuple}`: reasons why given board passed the test. This is *mutated* during the session.
|
|
|
|
## reason structure
|
|
|
|
One element of the `reason` should be like this:
|
|
|
|
```julia
|
|
(
|
|
name = "name of this category",
|
|
passing_pairs = NamedTuple[
|
|
(; psboard_id, note = "supplemental note"),
|
|
others...
|
|
],
|
|
)
|
|
```
|
|
"""
|
|
function scan_dispatchcheck(conn::DbConnection; reasons = NamedTuple[])
|
|
df_ps_boards = DBInterface.execute(conn.db, sql"select * from ps_boards") |> DataFrame
|
|
pushfirst!(reasons, (name = "add new reason", passing_pairs = NamedTuple[]))
|
|
|
|
for row_ps_boards in eachrow(df_ps_boards)
|
|
old_result = is_dispatchable(conn, row_ps_boards.id)
|
|
if !ismissing(old_result) && old_result
|
|
continue
|
|
end
|
|
if any(reasons) do reason
|
|
any(reason.passing_pairs) do passing_pair
|
|
matched = row_ps_boards.id == passing_pair.psboard_id
|
|
if matched
|
|
@info "psbid $(row_ps_boards.id) passed for $(reason.name)"
|
|
end
|
|
matched
|
|
end
|
|
end
|
|
continue
|
|
end
|
|
|
|
@info "missing: $(row_ps_boards.id)"
|
|
selected = request(
|
|
"select passing reason(press q to skip):",
|
|
RadioMenu([nt.name for nt in reasons]),
|
|
)
|
|
if selected == -1
|
|
println("none selected. keep missing")
|
|
elseif selected == 1
|
|
print("new reason name: ")
|
|
new_name = readline()
|
|
print("note for this case: ")
|
|
note = readline()
|
|
new_reason = (
|
|
name = new_name,
|
|
passing_pairs = NamedTuple[(psboard_id = row_ps_boards.id, note)],
|
|
)
|
|
push!(reasons, new_reason)
|
|
else
|
|
println("selected reason: $(reasons[selected].name)")
|
|
print("note for this case: ")
|
|
note = readline()
|
|
push!(reasons[selected].passing_pairs, (psboard_id = row_ps_boards.id, note))
|
|
end
|
|
end
|
|
@assert popfirst!(reasons).name == "add new reason"
|
|
end
|
|
|
|
end # module DispatchChecker
|