Saving material does not necessarily mean that a search was measured. I wrote a synthetic module that accepts web and mobile observation states separately. It performs no search or download. Its narrow responsibility is to check how supplied observations are declared and to avoid filling an absent position with zero or a large sentinel number.

Content saving and search observation are separate inputs

summarize accepts synthetic publisher and item identifiers, explicit web and mobile Observation objects, and a content-save declaration. content_saved must be an actual boolean. Even when it is true, two not_performed observations keep both positions as None. Conversely, seeing an item in search does not manufacture a declaration that its content file was saved.

Item identity is supplied explicitly through synthetic identifiers. The module does not normalize titles to guess identity or parse a real platform URL. The original program has a title-based fallback when identifiers are missing; this example deliberately rejects a missing identifier. It is a narrower contract rather than a copy of the original platform-specific identity rules.

The four states imply different next work

not_performed declares that the check was not done and accepts neither a position nor a checked range. not_found_in_scope requires a declared range and describes absence within it. unavailable describes a check that could not be completed; its range is the attempted window, not proof that the whole window was read. Only observed carries a supplied position.

None of these states authenticates an actual search page. The module calculates meaning and consistency over caller-supplied inputs. Trustworthy observations, search conditions, collection permission and site access remain responsibilities of the host. An absent value does not cause the code to guess which state occurred.

Reject numbers that do not represent a position in the declared range

An observed position must be a positive integer inside its declared start and end. Booleans, numeric strings, zero and out-of-range positions are rejected. Other states cannot carry a rank value. Moving an internal sorting sentinel into an observation would fail this contract. Sorting convenience and the meaning of a displayed position are separate decisions.

The upper bound is a limit for a small synthetic exercise. It does not claim that a search service provides that many results or that such a window is statistically adequate. Real comparison conditions need their own policy. The complete code is included below and contains no browser or filesystem reader for measuring actual positions.

Explore the implementation

Complete independent synthetic implementation

"""Declared synthetic observations, not search measurement or indexing proof."""
from dataclasses import dataclass


def alias(value):
    if not isinstance(value, str) or not value.strip() or len(value) > 80:
        raise ValueError('A bounded nonempty alias is required')


@dataclass(frozen=True)
class Observation:
    channel: str
    sample: str
    query: str
    state: str
    position: int | None = None
    start: int | None = None
    end: int | None = None

    def __post_init__(self):
        alias(self.sample); alias(self.query)
        if self.channel not in ('web', 'mobile'):
            raise ValueError('Unknown source channel')
        if self.state not in ('not_performed', 'not_found_in_scope', 'unavailable', 'observed'):
            raise ValueError('Unknown observation state')
        if self.state == 'not_performed':
            if any(v is not None for v in (self.position, self.start, self.end)):
                raise ValueError('An unperformed check has no measured rank or checked range')
            return
        if (type(self.start) is not int or type(self.end) is not int
                or not 1 <= self.start <= self.end <= 10000):
            raise ValueError('A bounded declared/requested range is required')
        if self.state == 'observed':
            if type(self.position) is not int or not self.start <= self.position <= self.end:
                raise ValueError('Observed position must be inside the declared range')
        elif self.position is not None:
            raise ValueError('No rank value without an observed position')


def _display(o):
    if o.state == 'observed':
        ko, en = str(o.position)+'위 (관측 범위)', 'Position '+str(o.position)+' in this observation'
    else:
        ko, en = {
            'not_performed': ('조사 안 함', 'Not checked'),
            'not_found_in_scope': ('조사 범위에서 찾지 못함', 'Not found in the declared scope'),
            'unavailable': ('확인 불가', 'Unavailable'),
        }[o.state]
    return {'state': o.state, 'position': o.position,
            'range': [o.start, o.end] if o.start is not None else None,
            'display_ko': ko, 'display_en': en}


def summarize(*, publisher, item, web, mobile, content_saved):
    alias(publisher); alias(item)
    if not isinstance(web, Observation) or not isinstance(mobile, Observation):
        raise ValueError('Both source observations must be explicit')
    if web.channel != 'web' or mobile.channel != 'mobile':
        raise ValueError('Channel slots cannot be swapped or duplicated')
    if (web.sample, web.query) != (mobile.sample, mobile.query):
        raise ValueError('Different observation samples or query contexts')
    if type(content_saved) is not bool:
        raise ValueError('Content-save declaration must be a boolean')
    return {'identity': {'publisher': publisher, 'item': item},
            'sample': web.sample, 'query': web.query,
            'content_saved_declared': content_saved,
            'web': _display(web), 'mobile': _display(mobile),
            'observed_channels': [o.channel for o in (web, mobile) if o.state == 'observed'],
            'observation_authenticated': False, 'global_rank_proven': False}

Match observation context even when the item is the same

Web and mobile observations must occupy their explicit channel slots and share sample and query aliases. This rejects inputs from different declared runs or queries. The channel ranges may differ and remain visible in the result. A web window of positions 21–30 and a mobile window of 1–10 are not silently treated as the same complete population.

The module creates no representative position by taking a minimum or average. observed_channels lists only channels declared as observed. Matching aliases still do not authenticate equal real-world time, location, personalization or methodology. A host needs a stronger binding for the actual comparison context; this code does not create that evidence on its behalf.

Read different meanings behind missing values in five records

The records show a direct download without a search check, two different observed positions, scoped absence on web, an unavailable web check, and scoped absence on both channels. The same None position can therefore carry different states and guidance. These are actual local Python outputs using synthetic inputs, not customer material or real search positions.

The selector displays saved results only. Its actual inline scripts are checked with DOM stubs for initial display and all five choices in each language. No new search request, file download or visitor-account access occurs. This verification does not become a test of a live search service or a physical mobile interface.

A read-only viewer of synthetic local records

Connect the evidence that produced an observation

Sixteen tests cover the separation of saving and search, two channel positions, the unperformed/absent/unavailable distinction, context mismatches, invalid ranges and types, and input preservation. The tests are repeated after ZIP extraction into a separate directory. Implementation, tests, a record generator, results, README and an MIT license are included.

observation_authenticated and global_rank_proven always remain false. The existing per-field evidence module is a separate component for examining support attached to a value; it is not automatically integrated with this package. A real collector, logs, permissions, conditions and retention policy must be connected and that integration tested within its own scope.

Download code, tests and records
Python standard library, synthetic data, modification and reuse permitted

Distinguish the native repair from this example’s role

The subsequent development task repaired the native collector so that two missing ranks produce UNRECORDED instead of WEB_ONLY. best_rank remains None, saved as JSON null or a blank CSV field, and display and saved HTML say that no rank is recorded. This corrects the original output error. It does not install the ZIP’s entire four-state model into the collector.

UNRECORDED alone must not be automatically mapped to not_performed or not_found_in_scope. Absence with an unknown cause differs from a declaration that a check never occurred. Integration with this example requires the host to establish whether checking happened and what was attempted or actually read. Without that evidence, defer the mapping or design a separate unknown state; do not infer detailed status from an empty field.

The native repair’s fifteen offline tests and the same checks on restored code are described in the separate maker account. This ZIP’s sixteen synthetic tests and result viewer remain unchanged. This revision explains the integration boundary without changing the public package bytes or existing input contract. It adds no claim of completed live-search, image-download or browser verification.

Do not pass a list number as an observed position

A further native repair addressed the whole-blog wrapper passing a post-list number into mobile_rank. It now keeps this response order separately as list_position and supplies None for search rank. The existing save engine writes UNRECORDED, null and neutral no-rank wording. This repairs the meaning of input to the earlier output fix; it does not install a new module.

An observed position in this four-state example needs supporting observation. Being a positive integer does not turn a list number into a measured position. Conversely, UNRECORDED alone must not become not_performed. A host that establishes that a path never calls search can explicitly declare that fact, but this repair includes no new adapter performing that mapping.

The later native checks comprise twelve actual-main/save tests and fifteen existing search regressions, the same twenty-seven tests on restored code, and eighty measured-rank merge combinations. This ZIP retains its sixteen synthetic tests, record viewer, bytes and input contract. The repair neither migrates historical saved files nor adds completed live-search, image or rendered-browser verification.