How I Built 9 Claude AI Features into a Production SaaS

Nine AI features. One production SaaS. Zero hallucinated data in the user’s face.

That last part is harder than it sounds.

This is a full technical breakdown of how I designed, built, and shipped nine Claude Haiku features into CitizenApp — a GDPR-compliant citizen management platform. I’ll cover the architecture, the security decisions, the failure modes I had to defend against, and what I’d do differently.

Why Haiku, Not Sonnet or Opus

Every AI feature in CitizenApp uses claude-haiku-4-5. The reasoning was deliberate:

Cost. This is a multi-tenant SaaS. Each AI call deducts from the tenant’s credit balance. Haiku is significantly cheaper per token than Sonnet — which means I can offer meaningful AI functionality at the Free tier without destroying margins.

Speed. Most features are interactive — the user is waiting. Haiku’s latency is typically under 2 seconds for the prompt sizes I’m sending. Sonnet would double or triple that.

Sufficient capability. For structured tasks — SQL generation, data summarisation, duplicate record comparison — Haiku is good enough. I don’t need Opus-level reasoning. I need fast, cheap, reliable JSON out.

Consistent timeout budget. Every AI call has a 20-second timeout. Haiku almost always completes in under 5. That matters when you’re running on Render’s free tier.

The Credit System

Before the features themselves: every AI call in CitizenApp costs credits. This is enforced at the API layer, not the frontend.

async def deduct_credits(db: AsyncSession, tenant_id: int, amount: int = 1) -> None:
    tenant = await db.get(TenantModel, tenant_id)
    if tenant.ai_credits < amount:
        raise HTTPException(status_code=402, detail="Insufficient AI credits")
    tenant.ai_credits -= amount
    await db.commit()

Simple. No credits, no AI call. The 402 Payment Required response tells the frontend to show the upgrade prompt. Credits are deducted before the Claude API call — if the call fails, I refund them. This prevents users from draining credits on broken requests.

Stats queries cost 1 credit. Live DB queries cost 2. The pricing reflects actual Claude token consumption.

Feature 1: CSV Column Mapping

The problem: Users import citizen data from spreadsheets. The column headers are inconsistent — TC No, tc_no, Kimlik No, ID Number — all mean the same thing. Asking users to manually map every import is a UX failure.

The implementation:

async def ai_map_columns(headers: list[str]) -> dict:
    prompt = f"""You are mapping CSV column headers to a fixed schema.

Schema fields: tc_no, ad_soyad, dogum_tarihi, cinsiyet
Headers: {headers}

Return JSON only:
{{"tc_no": "header or null", "ad_soyad": "header or null", 
  "dogum_tarihi": "header or null", "cinsiyet": "header or null"}}"""

    response = await anthropic_client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=200,
        messages=[{"role": "user", "content": prompt}]
    )
    return json.loads(response.content[0].text)

What I learned: Keep the schema description short and the output format rigid. Early versions asked Claude to “suggest the best mapping” — vague enough that it would sometimes return explanatory text instead of JSON. Specifying Return JSON only: with the exact structure eliminated this.

Failure mode handled: If json.loads throws, fall back to fuzzy string matching. AI is the first attempt, not the only one.

Feature 2: Natural Language Search

The problem: Users need to filter 50,000+ citizen records. SQL is not the interface.

The implementation:

ALLOWED_COLUMNS = {"ad_soyad", "dogum_tarihi", "cinsiyet", "created_at", "il", "ilce"}

async def nl_to_sql_filter(query: str, tenant_id: int) -> str:
    prompt = f"""Convert this natural language query to a PostgreSQL WHERE clause.

Table: vatandas
Available columns: {', '.join(ALLOWABLE_COLUMNS)}
tenant_id is always {tenant_id} — always include it.

Query: "{query}"

Return only the WHERE clause starting with 'WHERE'. No explanation."""

    response = await anthropic_client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=300,
        messages=[{"role": "user", "content": prompt}]
    )

    clause = response.content[0].text.strip()
    validate_sql_clause(clause)  # raises on dangerous patterns
    return clause

The critical part — validate_sql_clause:

BLOCKED = ["drop", "delete", "update", "insert", "alter", "exec", 
           "--", ";", "union", "sleep", "pg_"]

def validate_sql_clause(clause: str) -> None:
    lower = clause.lower()
    if not lower.startswith("where"):
        raise ValueError("Invalid clause")
    for pattern in BLOCKED:
        if pattern in lower:
            raise ValueError(f"Blocked pattern: {pattern}")

Never trust LLM output with SQL. The allowed columns list is in the prompt — if Claude generates a column not in that list, SQLAlchemy will raise a column-not-found error before anything executes. Defense in depth.

Feature 3: Anomaly Detection

The problem: Bad data accumulates. Future birthdates, 1890-born citizens, missing gender on 40% of records. Users need to find it without writing reports.

The implementation: I run five deterministic checks in Python — no AI involved in detecting the anomalies:

def detect_anomalies(citizens: list[dict]) -> list[dict]:
    issues = []
    today = date.today()

    for c in citizens:
        if c["dogum_tarihi"] and c["dogum_tarihi"] > today:
            issues.append({"id": c["id"], "type": "future_dob", "severity": "high"})
        if c["dogum_tarihi"] and c["dogum_tarihi"].year < 1900:
            issues.append({"id": c["id"], "type": "implausible_dob", "severity": "medium"})
        if not c["cinsiyet"]:
            issues.append({"id": c["id"], "type": "missing_gender", "severity": "low"})

    return issues

Claude’s role is to summarise the results:

summary_prompt = f"""Summarise these data quality issues in 2-3 sentences for a non-technical admin:
{json.dumps(issue_counts)}
Be specific about numbers. Suggest the highest-priority fix first."""

This is the right division of labour. Python finds the issues deterministically. Claude explains them in plain language. AI never makes the binary “is this an anomaly” judgment — it just communicates results.

Feature 4: AI Daily Briefing

The simplest feature architecturally. Every morning, the admin dashboard can show a 3-4 sentence Claude-generated briefing:

stats = {
    "total_records": 12847,
    "missing_gender": 342,
    "missing_dob": 89,
    "potential_duplicates": 23,
    "created_last_7_days": 156
}

prompt = f"""You are a data assistant. Write a 3-4 sentence briefing for an admin 
about the current state of their citizen database. Be concise and actionable.

Stats: {json.dumps(stats)}"""

Lesson: Don’t send raw PII to the API. Stats are safe. Individual citizen records are not. I aggregate before sending.

Feature 5: AI Duplicate Merge

This was the most complex feature to get right.

The problem: The duplicate detection identifies potential duplicates by similarity score. But merging them requires judgment — which record has the correct spelling? Which birthdate is more likely accurate?

The implementation:

async def ai_merge_suggest(record_a: dict, record_b: dict) -> dict:
    # Mask TC no before sending to Claude
    safe_a = {k: v for k, v in record_a.items() if k != "tc_no"}
    safe_b = {k: v for k, v in record_b.items() if k != "tc_no"}

    prompt = f"""Compare these two citizen records and suggest the best merged version.

Record A (id={record_a['id']}): {json.dumps(safe_a)}
Record B (id={record_b['id']}): {json.dumps(safe_b)}

Return JSON:
{{
  "keep_id": <id of the record to keep as primary>,
  "merged": {{"ad_soyad": "...", "dogum_tarihi": "YYYY-MM-DD", "cinsiyet": "E/K"}},
  "confidence": "high|medium|low",
  "reasoning": "one sentence explanation"
}}"""

TC identity number (TC kimlik no) is always masked before sending to Claude. This is non-negotiable. It’s PII, it’s regulated, and it’s not needed for the merge decision. The keep_id in the response tells the backend which record to promote — Claude never sees or decides on the TC number.

Claude’s output is never applied automatically. It’s a suggestion displayed to the admin, who clicks “Apply” to confirm. The AI is an advisor, not an actor.

Feature 6: Natural Language Reports

The problem: Admins need custom data exports. “Give me all female citizens born between 1960 and 1970 in Ankara” is not a filter the UI can anticipate.

The implementation: A dedicated /reports page accepts a natural language query, sends it to Claude for SQL filter generation (same pattern as Feature 2), shows a preview table, then allows CSV/TSV export — all client-side using Blob and URL.createObjectURL.

The backend generates the filtered dataset. The download happens in the browser. No server-side file generation, no temp file cleanup, no storage costs.

Features 7 & 8: AI Chat — Stats and Live DB Query

The floating chat widget was the most technically interesting feature.

Two modes in one endpoint:

  1. Stats mode — Claude answers questions from aggregate statistics only. “How many records are missing a birthdate?” The answer comes from pre-computed stats, not a live query.

  2. DB query mode — For questions that can’t be answered from stats, Claude generates a SQL SELECT statement that the backend executes directly.

The DB query mode required careful safety engineering:

SAFE_QUERY_RULES = """
Generate a PostgreSQL SELECT query. Hard rules:
- Only query table: vatandas
- Never include column: tc_no  
- Always filter: WHERE tenant_id = {tenant_id}
- Always add: LIMIT 100
- No subqueries, no JOINs to other tables
- No aggregations that could expose cross-tenant data
"""

def validate_generated_query(sql: str, tenant_id: int) -> str:
    sql_lower = sql.lower().strip()

    assert sql_lower.startswith("select"), "Must be SELECT"
    assert "tc_no" not in sql_lower, "tc_no is blocked"
    assert f"tenant_id = {tenant_id}" in sql_lower, "tenant_id filter required"
    assert "limit" in sql_lower, "LIMIT required"

    # Extract and enforce limit
    limit_match = re.search(r"limits+(d+)", sql_lower)
    if limit_match and int(limit_match.group(1)) > 100:
        sql = re.sub(r"limits+d+", "LIMIT 100", sql, flags=re.IGNORECASE)

    return sql

The query executes with a 5-second statement timeout at the database level. If Claude generates something slow, it gets cancelled.

Query result display: Results are shown in a collapsible QueryBlock with the SQL visible above the data table. Users can see exactly what ran. Transparency matters when AI is touching your database.

Feature 9: AI Audit Trail Explainer

The audit log records 30+ event types — citizen_created, bulk_import, login_failed, 2fa_enabled, etc. Non-technical admins can’t interpret raw event streams.

async def explain_audit_events(events: list[dict]) -> str:
    # Strip internal IDs and technical fields
    safe_events = [
        {"type": e["event_type"], "user": e["user_email"], "time": e["created_at"]}
        for e in events[:20]  # Last 20 events only
    ]

    prompt = f"""Summarise this audit log in plain language for a non-technical admin.
Note any unusual patterns (multiple failed logins, bulk operations, permission changes).
2-4 sentences maximum.

Events: {json.dumps(safe_events)}"""

Cap at 20 events. Don’t send the full log. Keep the token budget predictable.

What I’d Do Differently

1. Add structured output from the start.

Anthropic’s tool use / structured output API is cleaner than prompt engineering Return JSON only. I retrofitted it on some features. It should have been the default.

2. Cache more aggressively.

The daily briefing doesn’t need to be generated every page load. A 6-hour Redis cache with background refresh would cut AI costs significantly. Currently it regenerates on every dashboard open.

3. Separate AI service layer.

All nine features call Claude via a thin wrapper in app/services/ai.py. This is good for centralised error handling and logging. What I should have added sooner: a proper prompt registry so prompts are versioned and testable independently of the endpoint logic.

4. Test AI outputs, not just AI calls.

My test suite mocks the Anthropic client. That’s necessary but insufficient — it doesn’t catch prompt regressions. A small set of golden-output integration tests against real Claude (run weekly, not on every CI run) would give me confidence that prompt changes don’t silently break feature behaviour.

The Result

Nine features, all in production, all credit-metered, all with safety validation on AI outputs. The platform handles real data for real users.

The engineering pattern that made this possible: AI generates, Python validates, humans confirm. The LLM is never the last line of defence.

Building AI features into your product? Let’s talk about what that looks like.

HashiCorp built an MCP server for writing Terraform. I built one for reviewing it

HashiCorp built an MCP server for writing Terraform. I built one for reviewing it.

A few weeks ago HashiCorp shipped terraform-mcp-server. It’s an official MCP server that lets a model lean on the Terraform Registry: search providers, pull module docs, manage HCP Terraform workspaces. The shape is “help the model author IaC.” That’s a genuinely useful tool and a clear signal that Terraform-via-MCP is now a category, not a curiosity.

But it doesn’t help with the part of Terraform I spend the most time stressed about: reviewing somebody else’s plan.

terraform plan outputs are long. Real production plans run into the thousands of lines. The risky changes (an IAM grant going *, a security group opening to 0.0.0.0/0, an RDS instance being replaced) hide between a hundred routine attribute updates. Code review tools don’t help because the danger isn’t in the HCL diff, it’s in the planned actions.

So I built tf-review-mcp. It’s an MCP server scoped to one job: parse terraform show -json output and surface what a human reviewer actually cares about, structured for an LLM to quote and reason about.

What it does

Two tools:

  • review_plan(plan_json_path) returns a structured summary: action counts, high-blast-radius resource changes, stateful destroys, and diff-aware public-exposure findings.
  • suggest_review_comments(plan_json_path) returns a list of {address, severity, comment} objects ready to drop into a PR review.

The server flags three things:

High-risk resource types (warn). A conservative built-in list across AWS, GCP, and Azure: IAM, KMS, RDS, security groups, S3, EKS, GKE, Cloud SQL, GCS, Cloud DNS, GCE firewalls, Key Vault. Easy to extend in source.

Stateful destroys (blocker). When a stateful resource like aws_db_instance, google_sql_database_instance, or google_compute_instance is scheduled for delete or replace. The GCE case matters more than people realize: a replace on a Compute Engine VM with local SSDs nukes both the boot disk and any local-SSD attachments. Silent data loss if a reviewer misses it.

Public exposure changes (blocker). Diff-aware. The server compares before and after on every google_compute_firewall change and fires when source_ranges newly contains 0.0.0.0/0 or ::/0. That single check has caught more dumb mistakes for me than any static analyzer.

What it looks like

Given a plan that deletes a Cloud SQL instance, replaces a GCE VM, and widens a firewall to the public internet, the server returns:

{
  "counts": {"create": 1, "update": 3, "replace": 1, "delete": 1},
  "stateful_destroys": [
    {"address": "google_compute_instance.op_geth", ...},
    {"address": "google_sql_database_instance.indexer", ...}
  ],
  "public_exposure_changes": [
    {"address": "google_compute_firewall.rpc_public",
     "finding": "source_ranges now includes 0.0.0.0/0 (public exposure)"}
  ],
  "notes": [
    "2 stateful resource(s) scheduled for destroy/replace. Verify backups and migration plan before applying.",
    "1 firewall change(s) widen public exposure. Confirm intent before applying."
  ]
}

Asked to summarize, Claude reads this and produces:

Blocker-severity items (3)

  • google_compute_instance.op_geth: Delete/recreate of a stateful compute instance. Confirm backup, migration, and rollback plan before merging.
  • google_sql_database_instance.indexer: Cloud SQL instance is being deleted. Confirm backup, migration, and rollback plan before merging.
  • google_compute_firewall.rpc_public: source_ranges now includes 0.0.0.0/0 (public exposure). Confirm this is intentional and matches firewall policy.

That’s a PR review comment I’d actually merge.

Why MCP, not a CLI

The same parser could be a CLI. The reason MCP matters is the conversational shape. When I’m reviewing a plan, I want to ask follow-up questions: “is the SQL delete a replace or a hard destroy?”, “what changed about that firewall?”, “draft me a PR comment for the IAM grant.” The model can pull the right tool, narrow on the right resource, and write something useful, because the underlying tool returns data, not prose. Lower hallucination surface. Higher signal per token.

Most community Terraform-MCP experiments wrap the CLI (“run terraform plan for me”). That’s the wrong abstraction for review. You don’t want the model running plan; you want it reasoning about a plan that already ran.

Architecture and deployment

The parser is in review.py as pure functions with no MCP imports: plan JSON in, ReviewSummary out. The MCP wrapper is in server.py, about a hundred lines. The split means the parser can be unit-tested, dropped into CI, or audited without touching the transport.

Deployment is local stdio. Always. The MCP client launches the server as a child process. Plan JSON never leaves your laptop. No auth, no shared state, no network listener.

Plan JSON contains IAM relationships, account IDs, security group rules, and resource counts. Hosted “send us your plan” services are a recon goldmine waiting to happen. The architecture should make leakage impossible by default. A self-hosted HTTP variant for CI is a v2 idea, with auth and a path allowlist, not before.

Why this is differentiated

HashiCorp’s official server covers Registry lookup and HCP workspace management. Helpful for writing.

This one covers plan review. Helpful for reviewing.

Both ship as MCP servers. Both can be installed in the same client. The model picks the right tool per task. They’re complementary by design, not competitive.

I haven’t found another MCP server scoped specifically to plan review with LLM-friendly structured output, as of writing. If one exists, I want to know.

What’s next

  • estimate_cost_delta wrapping Infracost.
  • check_policy wrapping Conftest, so teams can bring their own Rego.
  • Configurable HIGH_RISK_TYPES via a YAML file so each team can codify its own blast-radius rules.
  • More diff-aware checks: aws_security_group ingress widening, IAM * grants, google_storage_bucket force_destroy toggles.

The repo lives here: github.com/sanjeevkkansal/tf-review-mcp. It’s MIT-licensed, v0.2, and very much open to PRs, especially if you have a war story about a plan that should have been caught.

Sanjeev Kumar is an infrastructure engineer working on platform tooling, AI-assisted ops, and infra that doesn’t wake people up at 3am.

Why Enterprise AI Agent Deployments Keep Failing

In 2026, the gap between AI agent demos and AI agent deployments is the most expensive gap in enterprise technology. We’ve watched organizations spin up promising pilots, hit a wall somewhere between 10 and 50 concurrent tasks, and quietly shelve the project. According to McKinsey’s The State of AI in the Enterprise 2024, organizations are moving beyond pilots into production deployments, but successful implementations require clear governance frameworks and integration with existing business processes. Most teams skip both. That’s where things break.

This isn’t a theoretical problem. We built our first Autonomous SDR on a flat 3-agent architecture: research, scoring, and writing all reporting to a single orchestrator. It worked on 5 leads. At 50, the scorer sat idle waiting on research that had nothing to do with scoring. Splitting into discrete agents with explicit handoff contracts between them cut end-to-end processing time and made each component independently testable. We learned the hard way that implicit data passing doesn’t hold up when volume increases. Every pipeline we’ve built since uses explicit inter-agent schemas for exactly that reason.

Here are the five failure modes we see most often, and what separates the deployments that survive from the ones that don’t.

Failure Mode 1: Flat Architectures That Can’t Distribute Work

The first mistake is treating an agent system like a single smart function. Teams wire a reasoning model to a set of tools, give it a task, and call it an agent. This works in demos. It breaks when tasks have independent sub-components that don’t need to wait on each other.

The fix isn’t complexity for its own sake. It’s identifying which parts of a workflow are genuinely sequential and which can run in parallel. In our SDR build, research and lead enrichment could run concurrently. Scoring depended on research output. Writing depended on scoring. Once we mapped those dependencies explicitly, the architecture became obvious. The orchestrator’s job shrank to routing and handoff validation, not doing everything itself.

If you’re building multi-step pipelines in n8n or a similar orchestration tool, this dependency mapping should happen before you write a single node. Draw the data flow first.

Failure Mode 2: No Governance Model Before Deployment

Governance sounds like a compliance checkbox. In practice, it’s the set of rules that determines what an agent is allowed to do, what it must escalate, and who owns the output. Without it, agents make decisions that no one authorized and that no one can audit after the fact.

McKinsey’s 2024 research is direct on this point: governance frameworks and integration with existing business processes are what separate successful production deployments from failed ones. The organizations getting this right aren’t building governance after deployment. They’re defining it as part of the system design, before a single API call goes live.

Concretely, this means: defining which actions require human approval, logging every decision with enough context to reconstruct why it was made, and setting hard limits on what the system can modify without confirmation. These aren’t optional features. They’re the difference between a system your legal and security teams will approve and one they’ll shut down.

Failure Mode 3: Routing Every Task Through a Reasoning Model

This one is expensive and slow. A reasoning model is the right tool for ambiguous, multi-step decisions. It is the wrong tool for classification, formatting, or any task with a deterministic answer given structured input.

We’ve seen pipelines where every step, including simple data transformations, routes through a large LLM. The latency compounds. The cost compounds. And the output quality doesn’t improve because the task didn’t need that level of reasoning to begin with. A classification model or a rules-based node handles most of those steps faster and more consistently.

The honest tradeoff here: tiered routing adds architectural complexity. You need to define clear criteria for which tasks go where, and you need to test those criteria against real inputs. That’s real engineering work. For smaller deployments with low task volume, a single reasoning layer may be simpler to maintain even if it’s less efficient. The optimization is worth it when volume justifies the overhead.

Failure Mode 4: Security as an Afterthought

Agents that can read, write, and send on behalf of users are a different security surface than traditional software. They act. That means the attack surface includes not just the system itself but every downstream action the system can take.

Prompt injection is the most immediate concern: an adversarial input that redirects the agent’s behavior. Data exfiltration through tool calls is the second. Most enterprise security reviews weren’t designed with agentic systems in mind, which means teams are often deploying systems that haven’t been evaluated against the right threat model.

We cover the specific patterns we use for access scoping and input validation in our analysis of enterprise tool security. The short version: agents should operate with the minimum permissions required for their specific task, and every external input should be treated as untrusted until validated.

Failure Mode 5: No Observability Until Something Breaks

An agent system without observability is a black box. You know what went in and what came out. You don’t know why a particular decision was made, which step introduced an error, or whether the system is degrading gradually over time.

This matters more for agents than for traditional software because agent behavior is probabilistic. The same input can produce different outputs across runs. Without logging at the decision level, not just the input/output level, you can’t distinguish a one-off anomaly from a systematic failure pattern.

The minimum viable observability setup logs: the input to each agent component, the reasoning or classification output, the action taken, and the result. That’s four data points per step. For a 5-step pipeline, that’s 20 log entries per run. It feels like overhead until the first time you need to debug a failure that happened 3,000 runs ago.

For teams building these pipelines in n8n, the ForgeWorkflows blueprint catalog includes systems with observability hooks built into the architecture, so you’re not retrofitting logging after the fact.

What the Winning Deployments Have in Common

The organizations getting enterprise agent deployments right share three traits. They define governance before they write code. They design for failure, not just for the happy path. And they treat the agent architecture as a system to be maintained, not a project to be shipped.

The conference circuit in 2026, including CrewAI’s presence at Snowflake Summit, is full of impressive demos. Demos optimize for the happy path with clean inputs and cooperative conditions. Production systems face dirty data, adversarial inputs, and edge cases that no demo ever shows. The gap between those two environments is where most deployments fail.

If you’re evaluating where your organization sits on this spectrum, the distinction between AI tools and agentic systems is a useful starting point. The architectural decisions are different, and conflating the two leads to the exact failure modes described above.

What We’d Do Differently

Map inter-agent dependencies before choosing a framework. We spent two weeks refactoring the Autonomous SDR because we chose the orchestration pattern before we understood the data dependencies. The dependency map should come first. The framework choice follows from it, not the other way around.

Run a security review against an agentic threat model, not a traditional one. Standard application security reviews miss the action surface that agents introduce. We’d bring in a reviewer specifically familiar with prompt injection and tool-call abuse before any system touches production data, even in a limited pilot.

Build observability into the first prototype, not the second. Every time we’ve said “we’ll add logging later,” later has meant debugging a failure with no data. The cost of adding structured logging to a prototype is low. The cost of reconstructing what happened in a production failure without it is high.

Subnautica 2 satışlarda rekor kırdı

Subnautica 2

Subnautica 2Unknown Worlds bugün yaptığı açıklamada, Subnautica 2 oyunun 14 Mayıs’ta Erken Erişim’e çıkışından bu yana dünya çapında 4 milyondan fazla kopya sattığını duyurdu. Subnautica 2, piyasaya sürüldüğünde 1 milyon satış rakamını aşmış ve ilk 12 saat içinde 2 milyondan fazla kopya satmıştı. Oyun, piyasaya sürülmesinden sadece beş gün sonra dünya çapında 4 milyondan fazla kopya satmayı başardı ve Steam’deki eş zamanlı oyuncu sayısı 467.000’i geçti. Subnautica 2, an itibarıyla Steam’de 73.000’den fazla yorumda %91 olumlu değerlendirme oranıyla “Çok Olumlu” değerlendirme puanına sahip. Unknown Worlds ayrıca, Erken Erişim sürecinde gelecek güncellemelerin oyuncu geri bildirimlerine dayanacağının altını çizdi. Planlanan düzenlemeler arasında yaratıkların saldırganlığı ve tespit menzillerindeki değişikliklerin yanı sıra hayatta kalma araçlarında ve genel oyun deneyiminde iyileştirmeler yer alıyor. Subnautica 2 hakkında yapımcısından açıklama geldi Subnautica 2’nin Baş Yapımcısı Fernando Melo, “Dünyanın dört bir yanındaki oyunculardan gelen olumlu tepkilere son derece minnettarız,” dedi ve ekledi, “Erken Erişim’e çıkışımızla birlikte milyonlarca oyuncunun bu yepyeni dünyaya […]

Subnautica 2 satışlarda rekor kırdı yazısı ilk olarak Teknoloji haberleri – LOG üzerinde yayınlandı.

Date Shear: A New Term for a Common Programming Pain Point

One of the sneakiest, hardest to explain bugs. The process works fine any day you test it, but occasionally – and only occasionally it’s just… wrong. Test it again and it’s fine, no matter what day you test it.

This particular issue stems from a habit of grabbing a datetime whenever you need it, but coding as if you were getting the same value each time. It’s an easy to explain issue, but there’s not a term for it (as far as I can tell), so, I coined one: Date Shear.

What is Date Shear?

Date Shear (noun): Insidiously difficult to reproduce bugs that occur due to code fetching the “current time” multiple times, while the logic expects the same value. The result can be catastrophically incorrect behavior if the code is executed across a day, month or year boundary.

A Common Example

Imagine building a Quarterly report based on the current day:

  1. Just before midnight, a GetDate() or similar method gets the current datetime, and uses that to determine the start date for the “current” quarter.
  2. (Clock ticks midnight)
  3. Just after midnight, a GetDate() or similar method gets the current datetime, again (but it’s now in the next month) and uses that to determine the end date for the “current” quarter.

Now, you end up with the start of one quarter and – potentially – the end of the next quarter. This only happens if the request crosses into the next day, on the last day of the month, in the last month of a quarter.

Adding to the annoyance is that reports are often run by companies as part of an overnight batch process, increasing the likelihood of this occurring in a production environment… but testers are less likely to be testing functionality in the middle of the night – and daytime testing won’t reproduce the issue.

Why Naming This Matters

They say that “naming something gives it power”… but I would say that naming something gives you power over it. When we name a specific type of bug, we make it easier to communicate during code reviews and architectural planning. Instead of saying, “We need to double check the date handling to see if there’s something fishy in there,” you can simply say, “We need to guard against Date Shear here.”

The Solution for Date Shear

In simple single-script or single-method processes, the solution is simple. Grab a datetime at the beginning, store it in a variable and reuse it. In some cases, taking a parameter of the date in question is a good way to handle it.

For processes that call other processes (that may call further processes), taking the date in question in as a parameter allows them to all “be on the same page”.

What do you think? Have you encountered “Date Shear” in your own projects? Is there a better name for this that I’m somehow missing?

Avrupa Haber Ajansları Birliği Bahar Konferansı, Lahey'de yapıldı

Avrupa Haber Ajansları Birliği (EANA) Genel Kurulu ve Bahar Konferansı, Lahey’de ‘Dezenformasyon Dünyası: Haber Ajanslarının Karşılaştığı Zorluklar ve Çözüm Yolları’ temasıyla düzenlendi. AA Genel Müdürü Serdar Karagöz, dezenformasyonla mücadelede iş birliğinin önemini vurgularken, uzmanlar yalan haberin demokrasiye tehdit oluşturduğunu belirtti.