<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Konstantin</title>
    <description>The latest articles on DEV Community by Konstantin (@konstantinhr).</description>
    <link>https://dev.to/konstantinhr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4152390%2F2e30c2f1-24ab-4e1a-83af-aba617885fa8.png</url>
      <title>DEV Community: Konstantin</title>
      <link>https://dev.to/konstantinhr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/konstantinhr"/>
    <language>en</language>
    <item>
      <title>Remote job location rules: a TypeScript model for vacancy cards</title>
      <dc:creator>Konstantin</dc:creator>
      <pubDate>Sat, 03 Oct 2026 15:31:19 +0000</pubDate>
      <link>https://dev.to/konstantinhr/remote-job-location-rules-a-typescript-model-for-vacancy-cards-1lek</link>
      <guid>https://dev.to/konstantinhr/remote-job-location-rules-a-typescript-model-for-vacancy-cards-1lek</guid>
      <description>&lt;p&gt;Imagine two fictional vacancy cards. Both say “remote” and name the same city. One explicitly accepts applicants living elsewhere; the other says nothing about where an applicant may reside. A UI that turns both cards into “work from anywhere” has added information that was never supplied.&lt;/p&gt;

&lt;p&gt;This is a small domain-model problem with a visible career consequence. Work mode describes an arrangement. A city label describes a location. Eligibility to work from a particular country is another question. Treating them as one field makes the interface look simpler while making its meaning less precise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with what a card actually exposes
&lt;/h2&gt;

&lt;p&gt;A public &lt;a href="https://hireseeker.ru/vacancy/10045235-data-ai-engineer" rel="noopener noreferrer"&gt;Data/AI Engineer card on HireSeeker&lt;/a&gt; displays Kazakhstan, Astana and office work beside the role. These are distinct labels in an expanded vacancy view. Keeping these labels visible gives the reader several useful pieces of context before opening the original announcement.&lt;/p&gt;

&lt;p&gt;The example is useful to data engineers and backend developers for the same reason a typed API response is useful: separate fields let the reader ask separate questions. There is no need to derive a fictional remote condition from a real office-work label, or to make a city stand in for the employer's hiring policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give an unanswered question its own state
&lt;/h2&gt;

&lt;p&gt;Here is an illustrative TypeScript model for keeping these concepts separate in a vacancy interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Stated&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not-stated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;WorkMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;office&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hybrid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;remote&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;VacancyPlace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Stated&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Stated&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;workMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Stated&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WorkMode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;applicantResidenceRule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Stated&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;residenceText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;place&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VacancyPlace&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;place&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;applicantResidenceRule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Applicant residence conditions are not stated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important choice is not the particular property names. It is that &lt;code&gt;not-stated&lt;/code&gt; is representable without becoming an empty string, a universal permission, or a negative answer. The renderer can display what it knows and leave an unanswered question visible.&lt;/p&gt;

&lt;p&gt;The model deliberately does not produce an “eligible” Boolean. A vacancy interface cannot determine a specific person's eligibility from these labels alone. Nor does this example attempt to parse a legal rule or decide whether a restriction applies to an individual.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rendering without inventing a relationship
&lt;/h2&gt;

&lt;p&gt;For a fictional remote card, &lt;code&gt;workMode&lt;/code&gt; may be stated while &lt;code&gt;applicantResidenceRule&lt;/code&gt; remains &lt;code&gt;not-stated&lt;/code&gt;. The UI can render those two facts together: remote work is indicated; residence conditions are unspecified. Neither fact cancels the other.&lt;/p&gt;

&lt;p&gt;For a fictional hybrid card, a city may help describe the office location. It still does not specify the frequency of visits unless that condition is also supplied. A model that preserves independent fields lets later detail add meaning without rewriting what the original label said.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hireseeker.ru/" rel="noopener noreferrer"&gt;HireSeeker's public vacancy view&lt;/a&gt; provides a concrete place to observe location and work-mode presentation in an IT job product. The small model above asks a narrower engineering question than whether a role is attractive: can the interface distinguish a stated condition from a conclusion supplied by the interface itself?&lt;/p&gt;

&lt;p&gt;“Remote” is useful information. “Applicants may live anywhere” is different information. An unanswered residence question should remain unanswered until there is something that actually answers it.&lt;/p&gt;

</description>
      <category>remote</category>
      <category>software</category>
      <category>typescript</category>
      <category>ui</category>
    </item>
    <item>
      <title>Vacancy freshness is an event-time problem: a practical look at HireSeeker</title>
      <dc:creator>Konstantin</dc:creator>
      <pubDate>Wed, 30 Sep 2026 14:27:32 +0000</pubDate>
      <link>https://dev.to/konstantinhr/vacancy-freshness-is-an-event-time-problem-a-practical-look-at-hireseeker-3okh</link>
      <guid>https://dev.to/konstantinhr/vacancy-freshness-is-an-event-time-problem-a-practical-look-at-hireseeker-3okh</guid>
      <description>&lt;p&gt;A job listing can be new to an aggregator without being new to the job market. A recruiter can also refresh an old listing, and a company can publish the same role through several channels. A feed that treats all three events as “new vacancy” gives a developer a misleading queue.&lt;/p&gt;

&lt;p&gt;That makes job search an interesting application of a familiar data-engineering problem: keeping event time separate from processing time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hireseeker.ru/" rel="noopener noreferrer"&gt;HireSeeker&lt;/a&gt; provides a useful example of the product side. Its public interface brings IT and digital vacancies from Telegram, company career pages, LinkedIn, and other career platforms into one place. Profession filters, source links, favorites, and daily selections turn that collection into something a person can use without reading every source feed separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three clocks worth separating
&lt;/h2&gt;

&lt;p&gt;For a vacancy record, these timestamps answer different questions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Timestamp&lt;/th&gt;
&lt;th&gt;Question it answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source publication time&lt;/td&gt;
&lt;td&gt;When did this version of the listing become public?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First observation time&lt;/td&gt;
&lt;td&gt;When did this collector first encounter it?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last successful check&lt;/td&gt;
&lt;td&gt;When was its source last checked?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first timestamp may be missing or ambiguous. The second belongs to the collector. The third describes a check, not necessarily a change in the job itself.&lt;/p&gt;

&lt;p&gt;Suppose a crawler discovers a listing today whose source says it was published ten days ago. Sorting by first observation is reasonable for an “unseen by me” feed. Calling the same item a newly opened role would answer a different question with the wrong clock.&lt;/p&gt;

&lt;p&gt;A refresh introduces another distinction: the source can update a timestamp while keeping the responsibilities and requirements intact. A useful display can expose that event without pretending it is an entirely different job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deduplication should preserve provenance
&lt;/h2&gt;

&lt;p&gt;Two identical titles are not enough to identify the same opening. A company can hire several backend engineers at once. Conversely, one opening can appear as “Python Developer” on a job board and “Backend Engineer” in a recruiter’s Telegram message.&lt;/p&gt;

&lt;p&gt;A deduplication decision therefore needs more context: employer, role, location, responsibilities, source identifiers, and the relationship between versions. Even a good decision can lose useful information if it throws away every source except one.&lt;/p&gt;

&lt;p&gt;HireSeeker’s &lt;a href="https://hireseeker.ru/blog/kak-rabotaet-hireseeker" rel="noopener noreferrer"&gt;published collection and filtering methodology&lt;/a&gt; describes content-based duplicate detection and combining matching listings into a card with information about the sources. The useful part of that design is the retained route back to the original: the reader can check details where the vacancy was published.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small model for the UI
&lt;/h2&gt;

&lt;p&gt;An aggregator can expose freshness without collapsing its clocks. Here is an illustrative Python pattern; it is a design example, not HireSeeker’s implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;freshness_labels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vacancy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recent_window&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;vacancy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_seen_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;recent_window&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Recently added to this feed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vacancy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_published_at&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;recent_window&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Recently published at the source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;vacancy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source_updated_at&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Source reports an update&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example assumes valid timezone-aware timestamps. A production collector also needs to handle parsing failures, future dates, unavailable pages, and source-specific refresh semantics. Recording missing information explicitly is more useful than substituting the ingestion timestamp into every empty field.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes for a developer looking for work
&lt;/h2&gt;

&lt;p&gt;The practical advantage of one window is a shorter route from collection to a shortlist. HireSeeker offers profession selection for backend, frontend, mobile and game development, systems engineering, QA, analytics, ML and AI, alongside design, product and project management, marketing, and HR.&lt;/p&gt;

&lt;p&gt;After selecting a profession, the public interface exposes salary, source, language, and period filters. Favorites support a shortlist; a daily selection supports a regular review instead of continuous monitoring of job boards and Telegram feeds. Vacancy cards also offer resume adaptation and cover-letter generation, alongside links to the original listing.&lt;/p&gt;

&lt;p&gt;For a backend or QA search, a workable sequence is to select the profession, narrow the feed, save suitable roles, and open the originals before applying. The aggregation and filtering do the repetitive collection work. The original listing supplies the details needed for the application.&lt;/p&gt;

&lt;p&gt;The strongest design choice here is making a scattered search manageable in one place. Keeping provenance and freshness visible makes that convenience useful: readers can distinguish a newly discovered vacancy, an updated listing, and another copy of a role they have already reviewed.&lt;/p&gt;

</description>
      <category>career</category>
      <category>python</category>
    </item>
  </channel>
</rss>
