Summary
qrz_logbook_status returns dxcc: 0, start_date: "" and end_date: "" for logbooks that clearly have those values. count, confirmed and callsign are correct, so the tool looks like it is working — the wrong fields fail silently rather than erroring.
Found while verifying the fix for #3 against a live logbook.
Root cause
LogbookClient.status() reads key names that the QRZ ACTION=STATUS response does not use.
Verbatim live response (key redacted, PU2UMK, 30 QSOs):
CONFIRMED=3&COUNT=30&CALLSIGN=PU2UMK&BOOK_NAME=PU2UMK Logbook&END_DATE=2044-07-25&START_DATE=2024-09-03&ACTION=STATUS&OWNER=PU2UMK&DXCC_COUNT=1&RESULT=OK&BOOKID=406135
src/qrz_mcp/logbook_client.py:179-187 reads:
return LogbookStatus(
callsign=kv.get("OWNER", ""),
count=_int("COUNT"),
confirmed=_int("CONFIRMED"),
dxcc=_int("DXCC"), # response has DXCC_COUNT
us_states=_int("US_STATES"), # see note below
start_date=kv.get("START", ""), # response has START_DATE
end_date=kv.get("END", ""), # response has END_DATE
)
_int() defaults to "0" on a missing key and kv.get(..., "") defaults to empty, so every mismatch degrades quietly instead of surfacing.
| output field |
code reads |
in live response |
live value |
result |
callsign |
OWNER |
yes |
PU2UMK |
ok |
count |
COUNT |
yes |
30 |
ok |
confirmed |
CONFIRMED |
yes |
3 |
ok |
dxcc |
DXCC |
no (DXCC_COUNT) |
1 |
0 |
start_date |
START |
no (START_DATE) |
2024-09-03 |
"" |
end_date |
END |
no (END_DATE) |
2044-07-25 |
"" |
us_states |
US_STATES |
not present |
— |
0 (unconfirmed, see below) |
Response keys currently ignored: BOOKID, BOOK_NAME, CALLSIGN, DXCC_COUNT, END_DATE, START_DATE.
Reproduction
result = qrz_logbook_status(persona="...")
# {'callsign': 'PU2UMK', 'count': 30, 'confirmed': 3,
# 'dxcc': 0, <- response says DXCC_COUNT=1
# 'us_states': 0,
# 'start_date': '', <- response says START_DATE=2024-09-03
# 'end_date': ''} <- response says END_DATE=2044-07-25
Why tests miss it
_MOCK_STATUS_BODY (logbook_client.py:110) encodes the expected names rather than the API's actual ones:
_MOCK_STATUS_BODY = "RESULT=OK&COUNT=1547&DXCC=142&US_STATES=48&CONFIRMED=892&OWNER=KI7MT&START=20180101&END=20260301"
Every one of DXCC, US_STATES, START, END exists in the mock and none exist on the wire, so QRZ-L2-036/037 pass while live calls return zeros. Same root pattern as #3: the fixture encodes what the code wants, not what QRZ sends.
Suggested fix
Read the documented names, ideally with a fallback so both spellings work:
def _first(*keys: str) -> str:
for k in keys:
if k in kv:
return kv[k]
return ""
dxcc=_int_of(_first("DXCC_COUNT", "DXCC")),
start_date=_first("START_DATE", "START"),
end_date=_first("END_DATE", "END"),
callsign=_first("OWNER", "CALLSIGN"),
and update _MOCK_STATUS_BODY to the real wire format so the fixture can catch this class of drift.
Two things worth deciding, since they are judgement calls rather than clear bugs:
us_states is unconfirmed. No US-states-like key appears in my response, but this logbook has worked 0 US states, so QRZ may simply omit zero-valued keys. I can't tell a rename from an omission with this data. Someone with a US logbook could settle it in one call — the answer changes whether this needs a rename or is already correct.
OWNER vs CALLSIGN. Both are present and identical here. They may diverge for shared or multi-operator books, so which one callsign should mean is a semantic choice for the maintainer.
Note
Distinct from #3 (HTML-escaped ADIF), which only affects fetch/download. status parses fine — it just looks up the wrong keys. Deliberately left out of #4 to keep that diff scoped. Happy to send a PR.
AI disclosure
This issue was written by an AI agent (Claude, via OpenCode) working from a human-directed prompt, and was found while verifying the fix for #3 against a live logbook using maintainer-supplied credentials.
All values quoted above were read from actual API responses, not inferred. Where the data could not settle a question — the us_states field — that is stated as unresolved rather than guessed.
Summary
qrz_logbook_statusreturnsdxcc: 0,start_date: ""andend_date: ""for logbooks that clearly have those values.count,confirmedandcallsignare correct, so the tool looks like it is working — the wrong fields fail silently rather than erroring.Found while verifying the fix for #3 against a live logbook.
Root cause
LogbookClient.status()reads key names that the QRZACTION=STATUSresponse does not use.Verbatim live response (key redacted,
PU2UMK, 30 QSOs):src/qrz_mcp/logbook_client.py:179-187reads:_int()defaults to"0"on a missing key andkv.get(..., "")defaults to empty, so every mismatch degrades quietly instead of surfacing.callsignOWNERPU2UMKcountCOUNT30confirmedCONFIRMED3dxccDXCCDXCC_COUNT)10start_dateSTARTSTART_DATE)2024-09-03""end_dateENDEND_DATE)2044-07-25""us_statesUS_STATES0(unconfirmed, see below)Response keys currently ignored:
BOOKID,BOOK_NAME,CALLSIGN,DXCC_COUNT,END_DATE,START_DATE.Reproduction
Why tests miss it
_MOCK_STATUS_BODY(logbook_client.py:110) encodes the expected names rather than the API's actual ones:Every one of
DXCC,US_STATES,START,ENDexists in the mock and none exist on the wire, soQRZ-L2-036/037pass while live calls return zeros. Same root pattern as #3: the fixture encodes what the code wants, not what QRZ sends.Suggested fix
Read the documented names, ideally with a fallback so both spellings work:
and update
_MOCK_STATUS_BODYto the real wire format so the fixture can catch this class of drift.Two things worth deciding, since they are judgement calls rather than clear bugs:
us_statesis unconfirmed. No US-states-like key appears in my response, but this logbook has worked 0 US states, so QRZ may simply omit zero-valued keys. I can't tell a rename from an omission with this data. Someone with a US logbook could settle it in one call — the answer changes whether this needs a rename or is already correct.OWNERvsCALLSIGN. Both are present and identical here. They may diverge for shared or multi-operator books, so which onecallsignshould mean is a semantic choice for the maintainer.Note
Distinct from #3 (HTML-escaped ADIF), which only affects
fetch/download.statusparses fine — it just looks up the wrong keys. Deliberately left out of #4 to keep that diff scoped. Happy to send a PR.AI disclosure
This issue was written by an AI agent (Claude, via OpenCode) working from a human-directed prompt, and was found while verifying the fix for #3 against a live logbook using maintainer-supplied credentials.
All values quoted above were read from actual API responses, not inferred. Where the data could not settle a question — the
us_statesfield — that is stated as unresolved rather than guessed.