Spaces:
Sleeping
Sleeping
Download app/models.py from Conradf/nullmap-api: direct link, hf CLI and curl.
- Browser
- Download file 11.8 kB
-
https://huggingface.co/spaces/Conradf/nullmap-api/resolve/main/app/models.py
- Command line
-
hf download hf://spaces/Conradf/nullmap-api/app/models.py
-
curl -L -o models.py https://huggingface.co/spaces/Conradf/nullmap-api/resolve/main/app/models.py
11.8 kB
| """Validated public requests and evidence-constrained LLM schemas.""" | |
| from typing import Annotated, Literal | |
| from pydantic import BaseModel, ConfigDict, Field, model_validator | |
| ConceptTerm = Annotated[str, Field(min_length=2, max_length=200)] | |
| Bucket = Literal[ | |
| "effect", "credible_null", "reported_null", "inconclusive", "failed", "unreported" | |
| ] | |
| EffectType = Literal["SMD", "MD", "logOR", "logRR", "logHR"] | |
| class SearchFilters(BaseModel): | |
| """Corpus restrictions applied before the search runs. Every bound is independently optional. | |
| These narrow the full match set, not the ranking, so they also change bucket | |
| counts, the year histogram, the spin rate and the file-drawer share. The | |
| response therefore reports them back and the pipeline warns that the numbers | |
| describe a subset of the index. | |
| """ | |
| model_config = ConfigDict(extra="forbid", allow_inf_nan=False) | |
| yearFrom: int | None = Field(default=None, ge=1500, le=2100) | |
| yearTo: int | None = Field(default=None, ge=1500, le=2100) | |
| minCitations: int | None = Field(default=None, ge=0, le=10000000) | |
| maxCitations: int | None = Field(default=None, ge=0, le=10000000) | |
| def _bounds_are_ordered(self): | |
| if self.yearFrom is not None and self.yearTo is not None and self.yearFrom > self.yearTo: | |
| raise ValueError("yearFrom must not be later than yearTo.") | |
| if ( | |
| self.minCitations is not None | |
| and self.maxCitations is not None | |
| and self.minCitations > self.maxCitations | |
| ): | |
| raise ValueError("minCitations must not exceed maxCitations.") | |
| return self | |
| def active(self) -> bool: | |
| return any(value is not None for value in self.model_dump().values()) | |
| def bounds_citations(self) -> bool: | |
| return self.minCitations is not None or self.maxCitations is not None | |
| def bounds_dates(self) -> bool: | |
| return self.yearFrom is not None or self.yearTo is not None | |
| class ConceptSteer(BaseModel): | |
| """Concepts that aim the same search, rather than a search of their own. | |
| Each term is one more direction added to (or subtracted from) the question's | |
| own vector before retrieval, so the tags change what ranks highest among the | |
| question's matches. Subtracting is a direction, not a filter: a paper is | |
| pushed down for being about the term, never removed for mentioning it. | |
| """ | |
| model_config = ConfigDict(extra="forbid", str_strip_whitespace=True) | |
| positive: list[ConceptTerm] = Field(default_factory=list, max_length=8) | |
| negative: list[ConceptTerm] = Field(default_factory=list, max_length=8) | |
| def active(self) -> bool: | |
| return bool(self.positive or self.negative) | |
| class SearchRequest(BaseModel): | |
| model_config = ConfigDict(extra="forbid", str_strip_whitespace=True, allow_inf_nan=False) | |
| idea: str = Field(min_length=8, max_length=12000) | |
| field: str | None = Field(default=None, max_length=100) | |
| sesoi: float | None = Field(default=None, gt=0, le=1000) | |
| effectType: EffectType = "SMD" | |
| plannedN: int = Field(default=200, ge=4, le=1000000) | |
| alpha: float = Field(default=0.05, gt=0, lt=0.5) | |
| valueSuccess: float = Field(default=100, ge=0, le=1e12) | |
| valueNull: float = Field(default=20, ge=0, le=1e12) | |
| studyCost: float = Field(default=30, ge=0, le=1e12) | |
| outcomeSd: float | None = Field(default=None, gt=0, le=1e6) | |
| baselineRisk: float | None = Field(default=None, gt=0, lt=1) | |
| filters: SearchFilters | None = None | |
| concepts: ConceptSteer | None = None | |
| class Pico(BaseModel): | |
| population: str | |
| intervention: str | |
| comparator: str | |
| outcome: str | |
| synonyms: list[str] | |
| interventionAliases: list[str] = Field(default_factory=list) | |
| outcomeAliases: list[str] = Field(default_factory=list) | |
| populationAliases: list[str] = Field(default_factory=list) | |
| studyDesigns: list[str] | |
| sesoi: float = Field(gt=0) | |
| sesoiRationale: str | |
| effectType: EffectType | |
| class NumberEvidence(BaseModel): | |
| value: float | |
| quote: str | |
| class TextEvidence(BaseModel): | |
| value: str | |
| quote: str | |
| class ResultEvidence(BaseModel): | |
| """What the report itself states for its primary between-group comparison.""" | |
| value: Literal["positive", "null", "mixed"] | |
| quote: str | |
| Direction = Literal["favours_intervention", "favours_comparator", "unclear"] | |
| class DirectionEvidence(BaseModel): | |
| """Which arm the reported primary result favors; significance is a separate fact.""" | |
| value: Direction | |
| quote: str | |
| class BoolEvidence(BaseModel): | |
| value: bool | |
| quote: str | |
| class Extraction(BaseModel): | |
| """Every returned fact must have verbatim support in the ORIGINAL abstract.""" | |
| population: TextEvidence | None | |
| intervention: TextEvidence | None | |
| comparator: TextEvidence | None | |
| outcome: TextEvidence | None | |
| outcome_unit: TextEvidence | None | |
| design: TextEvidence | None | |
| n: NumberEvidence | None | |
| n_intervention: NumberEvidence | None | |
| n_comparator: NumberEvidence | None | |
| mean_intervention: NumberEvidence | None | |
| mean_comparator: NumberEvidence | None | |
| sd_intervention: NumberEvidence | None | |
| sd_comparator: NumberEvidence | None | |
| events_intervention: NumberEvidence | None | |
| events_comparator: NumberEvidence | None | |
| estimate: NumberEvidence | None | |
| ci_low: NumberEvidence | None | |
| ci_high: NumberEvidence | None | |
| ci_level: NumberEvidence | None | |
| ci_sides: TextEvidence | None | |
| p_value: NumberEvidence | None | |
| effect_type: TextEvidence | None | |
| primary_outcome_met: BoolEvidence | None | |
| reported_result: ResultEvidence | None | |
| result_direction: DirectionEvidence | None | |
| has_control: BoolEvidence | None | |
| class Relevance(BaseModel): | |
| """Ids of retrieved studies that actually address the research question.""" | |
| relevant_ids: list[str] | |
| class OutcomeGroup(BaseModel): | |
| label: str | |
| study_ids: list[str] | |
| class OutcomeGroups(BaseModel): | |
| """Studies judged to measure the same outcome for the same kind of comparison.""" | |
| groups: list[OutcomeGroup] | |
| class StudyNote(BaseModel): | |
| id: str | |
| note: str | |
| class Overview(BaseModel): | |
| """What the matched studies are and how each bears on the question.""" | |
| summary: str | |
| notes: list[StudyNote] | |
| class PicoChange(BaseModel): | |
| field: Literal["population", "intervention", "comparator", "outcome"] | |
| to: str | |
| reason: str | |
| class PicoRecommendation(BaseModel): | |
| """How the asked PICO should change, given what the record shows; empty when it should not.""" | |
| changes: list[PicoChange] | |
| rationale: str | |
| class EffectTrend(BaseModel): | |
| summary: str | |
| patterns: list[str] | |
| class Narrative(BaseModel): | |
| summary: str | |
| drivers: list[str] | |
| Coverage = Literal["tests_claim", "tests_related", "background_only"] | |
| NoveltyVerdict = Literal["already_done", "incremental", "open_gap", "insufficient_evidence"] | |
| class NoveltyRequest(BaseModel): | |
| model_config = ConfigDict(extra="forbid", str_strip_whitespace=True, allow_inf_nan=False) | |
| hypothesis: str = Field(min_length=8, max_length=4000) | |
| scan: int = Field(default=120, ge=10, le=400) | |
| read: int = Field(default=5, ge=1, le=12) | |
| class MapArithmetic(BaseModel): | |
| """Embedding arithmetic on idea phrases: start − remove + add. | |
| The terms are embedded separately and combined; the map then reports which | |
| real papers sit nearest the implied point. It is a way of asking "has this | |
| combination been run", not a proof that the analogy holds. | |
| """ | |
| model_config = ConfigDict(extra="forbid", str_strip_whitespace=True, allow_inf_nan=False) | |
| start: str = Field(min_length=2, max_length=4000) | |
| remove: list[Annotated[str, Field(min_length=1, max_length=500)]] = Field( | |
| default_factory=list, max_length=6 | |
| ) | |
| add: list[Annotated[str, Field(min_length=1, max_length=500)]] = Field( | |
| default_factory=list, max_length=6 | |
| ) | |
| def _has_an_operation(self): | |
| if not self.remove and not self.add: | |
| raise ValueError("An expression needs something to remove or to add.") | |
| return self | |
| class MapRequest(BaseModel): | |
| model_config = ConfigDict(extra="forbid", str_strip_whitespace=True, allow_inf_nan=False) | |
| idea: str | None = Field(default=None, min_length=8, max_length=4000) | |
| # When both are sent, the expression is placed and the idea is ignored. | |
| arithmetic: MapArithmetic | None = None | |
| cutoffYear: int | None = Field(default=None, ge=1900, le=2100) | |
| refresh: bool = False | |
| class ConceptSearchRequest(BaseModel): | |
| """Concepts to add and to subtract before the index is searched by nearest neighbor. | |
| The negatives are part of the query direction, not a post-filter: a paper is | |
| pushed away by a subtracted concept rather than removed for mentioning it. | |
| """ | |
| model_config = ConfigDict(extra="forbid", str_strip_whitespace=True, allow_inf_nan=False) | |
| positive: list[ConceptTerm] = Field(min_length=1, max_length=8) | |
| negative: list[ConceptTerm] = Field(default_factory=list, max_length=8) | |
| limit: int = Field(default=20, ge=1, le=50) | |
| filters: SearchFilters | None = None | |
| class Claim(BaseModel): | |
| """The testable content of a hypothesis, as retrieval terms and comparable facets.""" | |
| intervention: str | |
| system: str | |
| outcome: str | |
| direction: str | |
| queries: list[str] | |
| synonyms: list[str] | |
| class PaperRead(BaseModel): | |
| """What one paper's own results say about the claim, quoted from its text.""" | |
| coverage: Coverage | |
| system_tested: str | |
| intervention_tested: str | |
| outcome_measured: str | |
| finding: str | |
| quotes: list[str] | |
| facets_settled: list[str] | |
| facets_untested: list[str] | |
| class NoveltyAssessment(BaseModel): | |
| verdict: NoveltyVerdict | |
| summary: str | |
| gaps: list[str] | |
| settled: list[str] | |
| class IndexedNumberEvidence(BaseModel): | |
| value: float | |
| sentence_index: int = Field(ge=0) | |
| class IndexedTextEvidence(BaseModel): | |
| value: str | |
| sentence_index: int = Field(ge=0) | |
| class IndexedResultEvidence(BaseModel): | |
| value: Literal["positive", "null", "mixed"] | |
| sentence_index: int = Field(ge=0) | |
| class IndexedDirectionEvidence(BaseModel): | |
| value: Direction | |
| sentence_index: int = Field(ge=0) | |
| class IndexedBoolEvidence(BaseModel): | |
| value: bool | |
| sentence_index: int = Field(ge=0) | |
| class IndexedExtraction(BaseModel): | |
| """Select an original sentence; application code supplies its exact quote.""" | |
| population: IndexedTextEvidence | None | |
| intervention: IndexedTextEvidence | None | |
| comparator: IndexedTextEvidence | None | |
| outcome: IndexedTextEvidence | None | |
| outcome_unit: IndexedTextEvidence | None | |
| design: IndexedTextEvidence | None | |
| n: IndexedNumberEvidence | None | |
| n_intervention: IndexedNumberEvidence | None | |
| n_comparator: IndexedNumberEvidence | None | |
| mean_intervention: IndexedNumberEvidence | None | |
| mean_comparator: IndexedNumberEvidence | None | |
| sd_intervention: IndexedNumberEvidence | None | |
| sd_comparator: IndexedNumberEvidence | None | |
| events_intervention: IndexedNumberEvidence | None | |
| events_comparator: IndexedNumberEvidence | None | |
| estimate: IndexedNumberEvidence | None | |
| ci_low: IndexedNumberEvidence | None | |
| ci_high: IndexedNumberEvidence | None | |
| ci_level: IndexedNumberEvidence | None | |
| ci_sides: IndexedTextEvidence | None | |
| p_value: IndexedNumberEvidence | None | |
| effect_type: IndexedTextEvidence | None | |
| primary_outcome_met: IndexedBoolEvidence | None | |
| reported_result: IndexedResultEvidence | None | |
| result_direction: IndexedDirectionEvidence | None | |
| has_control: IndexedBoolEvidence | None | |