mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
fix(ownership): one release order everywhere; keep the old search API
From the Codex re-gate of the 0.3.3 fixes: - `changes` broke ties between same-date releases by row id, but the ticker, entity, and graph queries and the snapshot builder did not, so two files imported in the same second could resolve differently per query. Order by as_of_date, imported_at, then id everywhere. - search::fts_search and search::rebuild_fts were public in a published crate; keep them as deprecated wrappers. - Invalid --source and --by are INVALIDINPUT, like other argument errors. - The prefix-ranking test could not tell prefix from exact-alias ranking; add one where only prefix vs substring differs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
This commit is contained in:
parent
1d3fe5ca88
commit
6abc4a0712
5 changed files with 63 additions and 8 deletions
|
|
@ -555,7 +555,7 @@ impl FromStr for HolderSource {
|
||||||
"all" => Ok(Self::All),
|
"all" => Ok(Self::All),
|
||||||
"ksei" => Ok(Self::Ksei),
|
"ksei" => Ok(Self::Ksei),
|
||||||
"bing" => Ok(Self::Bing),
|
"bing" => Ok(Self::Bing),
|
||||||
_ => Err(IdxError::ParseError(
|
_ => Err(IdxError::InvalidInput(
|
||||||
"invalid --source, expected: all|ksei|bing".to_string(),
|
"invalid --source, expected: all|ksei|bing".to_string(),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
|
|
@ -588,7 +588,7 @@ impl FromStr for ConcentrationRank {
|
||||||
"top1" => Ok(Self::Top1),
|
"top1" => Ok(Self::Top1),
|
||||||
"top3" => Ok(Self::Top3),
|
"top3" => Ok(Self::Top3),
|
||||||
"hhi" => Ok(Self::Hhi),
|
"hhi" => Ok(Self::Hhi),
|
||||||
_ => Err(IdxError::ParseError(
|
_ => Err(IdxError::InvalidInput(
|
||||||
"invalid --by, expected: top1|top3|hhi".to_string(),
|
"invalid --by, expected: top1|top3|hhi".to_string(),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -797,7 +797,7 @@ pub fn query_releases(conn: &Connection) -> Result<Vec<OwnershipRelease>, IdxErr
|
||||||
.prepare(
|
.prepare(
|
||||||
"SELECT id, source_url, sha256, as_of_date, row_count, imported_at
|
"SELECT id, source_url, sha256, as_of_date, row_count, imported_at
|
||||||
FROM ownership_releases
|
FROM ownership_releases
|
||||||
ORDER BY as_of_date DESC, imported_at DESC",
|
ORDER BY as_of_date DESC, imported_at DESC, id DESC",
|
||||||
)
|
)
|
||||||
.map_err(|e| IdxError::DatabaseError(e.to_string()))?;
|
.map_err(|e| IdxError::DatabaseError(e.to_string()))?;
|
||||||
|
|
||||||
|
|
@ -1298,7 +1298,7 @@ fn latest_ksei_release(conn: &Connection) -> Result<Option<LatestKseiRelease>, I
|
||||||
conn.query_row(
|
conn.query_row(
|
||||||
"SELECT sha256, as_of_date
|
"SELECT sha256, as_of_date
|
||||||
FROM ownership_releases
|
FROM ownership_releases
|
||||||
ORDER BY as_of_date DESC, imported_at DESC
|
ORDER BY as_of_date DESC, imported_at DESC, id DESC
|
||||||
LIMIT 1",
|
LIMIT 1",
|
||||||
[],
|
[],
|
||||||
|row| {
|
|row| {
|
||||||
|
|
@ -1544,6 +1544,39 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn same_second_imports_resolve_to_the_later_file_everywhere() {
|
||||||
|
// Original and corrected file for one date imported in the same
|
||||||
|
// second: the ticker view and `changes` must read the same one.
|
||||||
|
let conn = setup();
|
||||||
|
let bbca = upsert_ticker(&conn, "BBCA", None).unwrap();
|
||||||
|
let alpha = entity(&conn, "ALPHA");
|
||||||
|
write_release(
|
||||||
|
&conn,
|
||||||
|
"jan",
|
||||||
|
"2026-01-30",
|
||||||
|
7,
|
||||||
|
bbca,
|
||||||
|
&[(Some(alpha), "ALPHA", 1000)],
|
||||||
|
);
|
||||||
|
write_release(
|
||||||
|
&conn,
|
||||||
|
"jan-fix",
|
||||||
|
"2026-01-30",
|
||||||
|
7,
|
||||||
|
bbca,
|
||||||
|
&[(Some(alpha), "ALPHA", 1200)],
|
||||||
|
);
|
||||||
|
|
||||||
|
let ticker = query_ticker_holdings(&conn, "BBCA").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
ticker.holders[0].percentage_bps, 1200,
|
||||||
|
"{:?}",
|
||||||
|
ticker.holders
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn changes_reject_a_date_without_a_release() {
|
fn changes_reject_a_date_without_a_release() {
|
||||||
let conn = setup();
|
let conn = setup();
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ fn query_all_edges(conn: &Connection) -> Result<Vec<GraphEdge>, IdxError> {
|
||||||
if let Ok(release_sha) = conn.query_row(
|
if let Ok(release_sha) = conn.query_row(
|
||||||
"SELECT sha256
|
"SELECT sha256
|
||||||
FROM ownership_releases
|
FROM ownership_releases
|
||||||
ORDER BY as_of_date DESC, imported_at DESC
|
ORDER BY as_of_date DESC, imported_at DESC, id DESC
|
||||||
LIMIT 1",
|
LIMIT 1",
|
||||||
[],
|
[],
|
||||||
|row| row.get::<_, String>(0),
|
|row| row.get::<_, String>(0),
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,19 @@ pub fn search_entities(
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Former name of [`search_entities`], kept for library users.
|
||||||
|
#[deprecated(since = "0.3.3", note = "use search_entities")]
|
||||||
|
pub fn fts_search(conn: &Connection, query: &str, limit: usize) -> Result<Vec<Entity>, IdxError> {
|
||||||
|
search_entities(conn, query, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search no longer uses an FTS index, so there is nothing to rebuild; kept
|
||||||
|
/// for library users.
|
||||||
|
#[deprecated(since = "0.3.3", note = "search reads entities and aliases directly")]
|
||||||
|
pub fn rebuild_fts(_conn: &Connection) -> Result<(), IdxError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use rusqlite::{Connection, params};
|
use rusqlite::{Connection, params};
|
||||||
|
|
@ -124,6 +137,15 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_name_prefix_ranks_above_a_mid_name_match() {
|
||||||
|
// Neither is an exact name or alias: only prefix vs substring differs,
|
||||||
|
// and the substring match is shorter, so length alone would flip them.
|
||||||
|
let conn = conn_with(&[("BANK MANDIRI TASPEN", &[]), ("PT BANK", &[])]);
|
||||||
|
|
||||||
|
assert_eq!(names(&conn, "bank"), ["BANK MANDIRI TASPEN", "PT BANK"]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn treats_like_wildcards_literally_and_empty_queries_as_no_match() {
|
fn treats_like_wildcards_literally_and_empty_queries_as_no_match() {
|
||||||
let conn = conn_with(&[("ALPHA_FUND", &[]), ("ALPHAXFUND", &[]), ("BETA 100%", &[])]);
|
let conn = conn_with(&[("ALPHA_FUND", &[]), ("ALPHAXFUND", &[]), ("BETA 100%", &[])]);
|
||||||
|
|
|
||||||
|
|
@ -79,9 +79,9 @@ fi
|
||||||
|
|
||||||
mkdir -p "$OUTPUT_DIR"
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
|
||||||
LATEST_AS_OF="$(sqlite3 "$DB_PATH" "SELECT as_of_date FROM ownership_releases ORDER BY as_of_date DESC, imported_at DESC LIMIT 1;")"
|
LATEST_AS_OF="$(sqlite3 "$DB_PATH" "SELECT as_of_date FROM ownership_releases ORDER BY as_of_date DESC, imported_at DESC, id DESC LIMIT 1;")"
|
||||||
LATEST_RELEASE_SHA="$(sqlite3 "$DB_PATH" "SELECT sha256 FROM ownership_releases ORDER BY as_of_date DESC, imported_at DESC LIMIT 1;")"
|
LATEST_RELEASE_SHA="$(sqlite3 "$DB_PATH" "SELECT sha256 FROM ownership_releases ORDER BY as_of_date DESC, imported_at DESC, id DESC LIMIT 1;")"
|
||||||
LATEST_ROW_COUNT="$(sqlite3 "$DB_PATH" "SELECT row_count FROM ownership_releases ORDER BY as_of_date DESC, imported_at DESC LIMIT 1;")"
|
LATEST_ROW_COUNT="$(sqlite3 "$DB_PATH" "SELECT row_count FROM ownership_releases ORDER BY as_of_date DESC, imported_at DESC, id DESC LIMIT 1;")"
|
||||||
RELEASE_COUNT="$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM ownership_releases;")"
|
RELEASE_COUNT="$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM ownership_releases;")"
|
||||||
TICKER_COUNT="$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM tickers;")"
|
TICKER_COUNT="$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM tickers;")"
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue