Spaces:
Running
Running
| """ | |
| Kinyarwanda number-to-words conversion endpoints. | |
| Ported from the kinya-ai-suite project's number2words engine (rule-based, | |
| no external ML dependency). All routes require the same platform token | |
| used by the rest of the Vibeon integration surface. | |
| """ | |
| from datetime import datetime as dt_type | |
| from fastapi import APIRouter, HTTPException, status, Depends | |
| from pydantic import BaseModel, Field, field_validator, ConfigDict | |
| from typing import Optional | |
| from services.kinyarwanda_numbers_service import NumberFormattingError, NumberService | |
| from utils.auth import verify_platform_token | |
| router = APIRouter(prefix="/numbers", tags=["kinyarwanda-numbers"]) | |
| number_service = NumberService() | |
| # --- Request schemas -------------------------------------------------------- | |
| class CardinalRequest(BaseModel): | |
| number: int = Field(..., description="Integer to convert to Kinyarwanda words", ge=0, le=999999999) | |
| class OrdinalRequest(BaseModel): | |
| number: int = Field(..., description="Integer to convert to ordinal", ge=1) | |
| noun_class: Optional[str] = Field(None, description="Noun class (class1, class9)", pattern="^(class1|class9)$") | |
| class CurrencyRequest(BaseModel): | |
| amount: float = Field(..., description="Currency amount", ge=0) | |
| currency_code: str = Field("RWF", description="Currency code (default: RWF)") | |
| class DateRequest(BaseModel): | |
| day: int = Field(..., description="Day of month", ge=1, le=31) | |
| month: int = Field(..., description="Month (1-12)", ge=1, le=12) | |
| year: int = Field(..., description="Year", ge=1) | |
| class TimeRequest(BaseModel): | |
| hour: int = Field(..., description="Hour (0-23, where 0 represents midnight)", ge=0, le=23) | |
| minute: int = Field(..., description="Minute (0-59)", ge=0, le=59) | |
| class DateTimeRequest(BaseModel): | |
| model_config = ConfigDict(populate_by_name=True) | |
| dt_value: dt_type = Field(..., description="Datetime to format", alias="datetime") | |
| style: str = Field("spoken", description="Format style: 'spoken' or 'full'") | |
| def validate_style(cls, v): | |
| if v not in ("spoken", "full"): | |
| raise ValueError("Style must be 'spoken' or 'full'") | |
| return v | |
| class FractionRequest(BaseModel): | |
| numerator: int = Field(..., description="Numerator", ge=1) | |
| denominator: int = Field(..., description="Denominator", ge=1) | |
| noun_class: Optional[str] = Field(None, description="Optional noun class") | |
| class WordsToNumberRequest(BaseModel): | |
| text: str = Field(..., description="Kinyarwanda number words to parse", min_length=1) | |
| # --- Response schemas -------------------------------------------------------- | |
| class CardinalResponse(BaseModel): | |
| number: int | |
| words: str | |
| success: bool = True | |
| class OrdinalResponse(BaseModel): | |
| number: int | |
| words: str | |
| noun_class: Optional[str] = None | |
| success: bool = True | |
| class CurrencyResponse(BaseModel): | |
| amount: float | |
| currency_code: str | |
| words: str | |
| success: bool = True | |
| class DateResponse(BaseModel): | |
| day: int | |
| month: int | |
| year: int | |
| words: str | |
| success: bool = True | |
| class TimeResponse(BaseModel): | |
| hour: int | |
| minute: int | |
| words: str | |
| success: bool = True | |
| class DateTimeResponse(BaseModel): | |
| datetime: str | |
| style: str | |
| words: str | |
| success: bool = True | |
| class FractionResponse(BaseModel): | |
| numerator: int | |
| denominator: int | |
| words: str | |
| success: bool = True | |
| class WordsToNumberResponse(BaseModel): | |
| text: str | |
| number: Optional[int] | |
| success: bool | |
| class NumberInfoResponse(BaseModel): | |
| model_name: str | |
| version: str | |
| description: str | |
| supported_features: dict | |
| success: bool = True | |
| # --- Routes -------------------------------------------------------- | |
| async def convert_cardinal(request: CardinalRequest, token: str = Depends(verify_platform_token)): | |
| try: | |
| words = number_service.get_cardinal(request.number) | |
| return CardinalResponse(number=request.number, words=words) | |
| except NumberFormattingError as e: | |
| raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) | |
| async def convert_ordinal(request: OrdinalRequest, token: str = Depends(verify_platform_token)): | |
| try: | |
| words = number_service.get_ordinal(request.number, request.noun_class) | |
| return OrdinalResponse(number=request.number, words=words, noun_class=request.noun_class) | |
| except NumberFormattingError as e: | |
| raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) | |
| async def format_currency_endpoint(request: CurrencyRequest, token: str = Depends(verify_platform_token)): | |
| try: | |
| words = number_service.format_currency_amount(request.amount, request.currency_code) | |
| return CurrencyResponse(amount=request.amount, currency_code=request.currency_code, words=words) | |
| except NumberFormattingError as e: | |
| raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) | |
| async def format_date_endpoint(request: DateRequest, token: str = Depends(verify_platform_token)): | |
| try: | |
| words = number_service.format_date_string(request.day, request.month, request.year) | |
| return DateResponse(day=request.day, month=request.month, year=request.year, words=words) | |
| except NumberFormattingError as e: | |
| raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) | |
| async def format_time_endpoint(request: TimeRequest, token: str = Depends(verify_platform_token)): | |
| try: | |
| words = number_service.format_time_string(request.hour, request.minute) | |
| return TimeResponse(hour=request.hour, minute=request.minute, words=words) | |
| except NumberFormattingError as e: | |
| raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) | |
| async def format_datetime_endpoint(request: DateTimeRequest, token: str = Depends(verify_platform_token)): | |
| try: | |
| words = number_service.format_datetime_string(request.dt_value, request.style) | |
| return DateTimeResponse(datetime=request.dt_value.isoformat(), style=request.style, words=words) | |
| except NumberFormattingError as e: | |
| raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) | |
| async def format_fraction_endpoint(request: FractionRequest, token: str = Depends(verify_platform_token)): | |
| try: | |
| words = number_service.format_fraction_string(request.numerator, request.denominator, request.noun_class) | |
| return FractionResponse(numerator=request.numerator, denominator=request.denominator, words=words) | |
| except NumberFormattingError as e: | |
| raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) | |
| async def words_to_number_endpoint(request: WordsToNumberRequest, token: str = Depends(verify_platform_token)): | |
| try: | |
| number = number_service.parse_words_to_number(request.text) | |
| return WordsToNumberResponse(text=request.text, number=number, success=number is not None) | |
| except Exception as e: | |
| raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error parsing words: {str(e)}") | |
| async def get_module_info(token: str = Depends(verify_platform_token)): | |
| info = number_service.get_module_info() | |
| return NumberInfoResponse( | |
| model_name=info.get("model_name", "kinyarwanda_number2words"), | |
| version=info.get("version", "1.0.0"), | |
| description=info.get("description", ""), | |
| supported_features=info.get("supported_features", {}), | |
| ) | |