diff --git a/stationapi/src/domain/ipa.rs b/stationapi/src/domain/ipa.rs index 4b16f0b7..1c204954 100644 --- a/stationapi/src/domain/ipa.rs +++ b/stationapi/src/domain/ipa.rs @@ -1,3 +1,27 @@ +/// Common katakana suffixes for line names, ordered longest-first for greedy matching. +const LINE_NAME_SUFFIXES: &[&str] = &["ホンセン", "シセン", "セン"]; +/// Suffixes that should NOT be stripped even though they end with セン. +const LINE_NAME_SUFFIX_EXCEPTIONS: &[&str] = &["シンカンセン"]; + +/// Strip a common line-name suffix (線/本線/支線) from a katakana string. +/// 新幹線 (Shinkansen) is preserved as it is used as-is in English. +/// Returns the stem (without the suffix). If no known suffix is found, returns the input unchanged. +pub fn strip_line_name_suffix(input: &str) -> &str { + for exception in LINE_NAME_SUFFIX_EXCEPTIONS { + if input.ends_with(exception) { + return input; + } + } + for suffix in LINE_NAME_SUFFIXES { + if let Some(stem) = input.strip_suffix(suffix) { + if !stem.is_empty() { + return stem; + } + } + } + input +} + /// Convert a katakana string to its IPA transcription. /// Returns `None` if the input contains characters that cannot be converted. pub fn katakana_to_ipa(input: &str) -> Option { @@ -608,4 +632,56 @@ mod tests { "dokkʲoːdaiɡakɯmae soːkamat͡sɯbaɾa" ); } + + // ============================================ + // strip_line_name_suffix tests + // ============================================ + + #[test] + fn test_strip_sen() { + assert_eq!( + strip_line_name_suffix("セイブイケブクロセン"), + "セイブイケブクロ" + ); + } + + #[test] + fn test_strip_honsen() { + assert_eq!( + strip_line_name_suffix("トウカイドウホンセン"), + "トウカイドウ" + ); + } + + #[test] + fn test_strip_shinkansen_preserved() { + // 新幹線(Shinkansen)は英語でもそのまま使われるので除去しない + assert_eq!( + strip_line_name_suffix("トウホクシンカンセン"), + "トウホクシンカンセン" + ); + } + + #[test] + fn test_strip_shisen() { + assert_eq!( + strip_line_name_suffix("ナガノハラクサツグチシセン"), + "ナガノハラクサツグチ" + ); + } + + #[test] + fn test_strip_no_suffix() { + // ライン等セン以外の末尾はそのまま返す + assert_eq!( + strip_line_name_suffix("ショウナンシンジュクライン"), + "ショウナンシンジュクライン" + ); + } + + #[test] + fn test_strip_bare_sen_returns_unchanged() { + // "セン" だけの場合、stemが空になるので除去しない + assert_eq!(strip_line_name_suffix("セン"), "セン"); + } } diff --git a/stationapi/src/use_case/dto/line.rs b/stationapi/src/use_case/dto/line.rs index f94c3d48..734e65c7 100644 --- a/stationapi/src/use_case/dto/line.rs +++ b/stationapi/src/use_case/dto/line.rs @@ -1,14 +1,15 @@ use crate::{ domain::{ entity::{gtfs::TransportType, line::Line}, - ipa::katakana_to_ipa, + ipa::{katakana_to_ipa, strip_line_name_suffix}, }, proto::{Line as GrpcLine, TransportType as GrpcTransportType}, }; impl From for GrpcLine { fn from(line: Line) -> Self { - let name_ipa = katakana_to_ipa(&line.line_name_k).filter(|ipa| !ipa.is_empty()); + let name_ipa = katakana_to_ipa(strip_line_name_suffix(&line.line_name_k)) + .filter(|ipa| !ipa.is_empty()); // バス路線の場合は line_type を OtherLineType (0) に強制 // (鉄道用の line_type が誤って設定されている可能性があるため) let line_type = if line.transport_type == TransportType::Bus {