Spaces:
Running
Running
| """Versioned public API models and opaque cursor contract.""" | |
| from __future__ import annotations | |
| import base64,json,re | |
| from typing import Any,Literal | |
| from pydantic import BaseModel,ConfigDict,Field | |
| REVISION=re.compile(r"^[0-9a-f]{40}$") | |
| class StrictModel(BaseModel): model_config=ConfigDict(extra="forbid") | |
| class ErrorBody(StrictModel): | |
| code:str;message:str;details:list[dict[str,Any]]=Field(default_factory=list);dataset_revision:str|None=None | |
| class ErrorResponse(StrictModel): error:ErrorBody | |
| class PageMeta(StrictModel): | |
| limit:int=Field(ge=1,le=100);next_cursor:str|None=None;dataset_revision:str;returned:int=Field(ge=0,le=100) | |
| class CompanySummary(StrictModel): | |
| issuer_id:str;exchange:Literal["ASX","NZX"];ticker:str;name:str|None=None;sector:str|None=None;industry:str|None=None;as_of_date:str;extraction_coverage_status:Literal["current","backfill_in_progress","pending","unknown"]="unknown";extraction_as_of_date:str|None=None;needs_review:bool;basic_shares:int|None=None;fd_ratio:float|None=None;worst_case_dilution_pct:float|None=None;expiry_pct_lt_3m:float|None=None;expiry_pct_lt_6m:float|None=None;expiry_pct_lt_12m:float|None=None;cash_runway_months:float|None=None | |
| class DilutionInstrument(StrictModel): | |
| instrument_id:str;ticker:str;instrument_type:str;quantity:int|None=None;series_name:str|None=None;asx_code:str|None=None;exercise_price_aud:str|None=None;expiry:str|None=None;holder_type:str|None=None;vesting_condition:str|None=None;status:str;as_of_date:str;document_ids:list[str]=Field(default_factory=list) | |
| class CompanySnapshot(StrictModel): | |
| dataset_revision:str;published_at:str|None=None;source_watermark:dict[str,Any]|None=None;dataset_citation:str;found:bool;company:CompanySummary|None=None;instruments:list[DilutionInstrument]=Field(default_factory=list);returned:int=Field(ge=0,le=100) | |
| class DocumentSummary(StrictModel): | |
| document_id:str;exchange:Literal["ASX","NZX"];ticker:str;source_document_id:str;announcement_date:str;announced_at:str|None=None;title:str;doc_type:str|None=None;doc_type_source:str|None=None;doc_type_confidence:float|None=None;raw_artifact_key:str|None=None;raw_size_bytes:int|None=None;markdown_artifact_key:str|None=None;markdown_size_bytes:int|None=None;publication_state:str;source_link:str|None=None | |
| class CompanyPage(StrictModel): items:list[CompanySummary];page:PageMeta | |
| class DocumentPage(StrictModel): items:list[DocumentSummary];page:PageMeta | |
| class SearchHit(StrictModel): | |
| document_id:str;ticker:str;doc_type:str|None=None;announcement_date:str;title:str;source_link:str;snippet:str=Field(max_length=600);score:float | |
| class SearchPage(StrictModel): items:list[SearchHit];page:PageMeta;query:str | |
| class EventItem(StrictModel): | |
| event_id:str;instrument_id:str|None=None;instrument_type:str|None=None;event_type:str|None=None;quantity_delta:int|None=None;effective_date:str|None=None;document_id:str|None=None;doc_type:str|None=None;status:str|None=None | |
| class EventPageMeta(StrictModel): | |
| limit:int=Field(ge=1,le=500);next_cursor:str|None=None;dataset_revision:str;returned:int=Field(ge=0,le=500) | |
| class EventPage(StrictModel): items:list[EventItem];page:EventPageMeta | |
| class DatasetStatus(StrictModel): | |
| service:str;ready:bool;status:str;build_sha:str;dataset_revision:str;schema_version:int;cache_artifacts:int;cache_bytes:int;documents_total:int|None=None;documents_last_24h:int|None=None;companies_total:int|None=None | |
| class CursorError(ValueError): | |
| def __init__(self,code,message):super().__init__(message);self.code=code | |
| def encode_cursor(revision:str,kind:str,values:list[Any])->str: | |
| if not REVISION.fullmatch(revision):raise ValueError("invalid revision") | |
| raw=json.dumps({"v":1,"revision":revision,"kind":kind,"values":values},sort_keys=True,separators=(",", ":")).encode() | |
| return base64.urlsafe_b64encode(raw).decode().rstrip("=") | |
| def decode_cursor(value:str,revision:str,kind:str)->list[Any]: | |
| if not value or len(value)>1024:raise CursorError("invalid_cursor","Cursor is empty or too long") | |
| try: | |
| raw=base64.b64decode(value+"="*(-len(value)%4),altchars=b"-_",validate=True);payload=json.loads(raw) | |
| except Exception as error:raise CursorError("invalid_cursor","Cursor is not valid base64url JSON") from error | |
| if set(payload)!={"v","revision","kind","values"} or payload["v"]!=1 or not isinstance(payload["values"],list):raise CursorError("invalid_cursor","Cursor shape is invalid") | |
| if payload["kind"]!=kind:raise CursorError("invalid_cursor","Cursor belongs to another endpoint") | |
| if payload["revision"]!=revision:raise CursorError("stale_cursor","Cursor Dataset revision is no longer active") | |
| return payload["values"] | |