
============================================================
 SL-004 — GUARDED PATCH + REGRESSION GATE
============================================================
UTC=2026-09-10T15:07:40Z
HOST=Azeroth
RC=/data/files/bmw/stocklab-release-candidate-sl003-final-20260910-111453

=== 1. HARD SAFETY CHECKS ===

=== 2. PRODUCTION SNAPSHOT — BEFORE ===
stocklab-redis	docker.io/library/redis:7-alpine	Up 33 hours (healthy)
stocklab-db	docker.io/library/postgres:16-alpine	Up 32 hours (healthy)
stocklab-release-qa-redis	docker.io/library/redis:7	Up 26 hours
stocklab-frontend	localhost/stocklab-frontend:release-qa-20260909-182234	Up 24 hours (healthy)
stocklab-backend	localhost/stocklab-backend:screener-percent-final-20260909-194639	Up 21 hours (healthy)
stocklab-worker	localhost/stocklab-backend:screener-percent-final-20260909-194639	Up 21 hours (healthy)
stocklab-beat	localhost/stocklab-backend:screener-percent-final-20260909-194639	Up 21 hours

Production ports:
LISTEN 0      2048       127.0.0.1:18000      0.0.0.0:*    users:(("uvicorn",pid=1574355,fd=11))                                                                                                                                                             
LISTEN 0      511        127.0.0.1:6379       0.0.0.0:*    users:(("redis-server",pid=1133711,fd=6))                                                                                                                                                         
LISTEN 0      200        127.0.0.1:5432       0.0.0.0:*    users:(("postgres",pid=1141225,fd=6))                                                                                                                                                             
LISTEN 0      511        127.0.0.1:13000      0.0.0.0:*    users:(("next-server (v1",pid=1497838,fd=18))                                                                                                                                                     

Production image IDs:
stocklab-backend image=localhost/stocklab-backend:screener-percent-final-20260909-194639 id=6efaac7ac11786530a884f942608c03222ebe725963fd1e1b2aae4892c1b2178 restart=0
stocklab-worker image=localhost/stocklab-backend:screener-percent-final-20260909-194639 id=6efaac7ac11786530a884f942608c03222ebe725963fd1e1b2aae4892c1b2178 restart=0
stocklab-beat image=localhost/stocklab-backend:screener-percent-final-20260909-194639 id=6efaac7ac11786530a884f942608c03222ebe725963fd1e1b2aae4892c1b2178 restart=0
stocklab-db image=docker.io/library/postgres:16-alpine id=75f5a96988cdf694a215073c3e9c001b706b371e2f94df3967f2efdec2787f6b restart=0
stocklab-redis image=docker.io/library/redis:7-alpine id=5509c0097c6064aa8a3b1df58f1d950e67090fffa6678ae8f3f1dc2385f12deb restart=0
stocklab-frontend image=localhost/stocklab-frontend:release-qa-20260909-182234 id=57db83ea090060258de97b86c93a6ac7c3185271f18a77a6e0630898b00ecd4e restart=0

=== 3. EXACT DATAQUALITY CONTRACT ===

=== 4. EXACT INGEST MERGE/PERSISTENCE CONTRACT ===
    for p in income_periods:
        fp = db.query(FinancialPeriod).filter_by(
            security_id=security.id, period_end=p.period_end, period_type=p.period_type, filing_date=p.filing_date,
        ).one_or_none()
        if fp is None:
            fp = FinancialPeriod(
                security_id=security.id, period_end=p.period_end, period_type=p.period_type,
                filing_date=p.filing_date, currency=p.currency, source_id=source.id,
            )
            db.add(fp)
            db.flush()

        key = (p.period_end, p.period_type)
        li = merge_statement_line_items(
            p.line_items,
            balance_by_period.get(key),
            cash_by_period.get(key),
        )
        secondary_p = secondary_by_period.get(key)
        li_b = (
            merge_statement_line_items(
                secondary_p.line_items, secondary_balance.get(key), secondary_cash.get(key)
            )
            if secondary_p is not None
            else None
        )
        secondary_tier = secondary_adapter.tier if (secondary_adapter is not None and li_b is not None) else None

        income = fp.income_statement or IncomeStatement(financial_period_id=fp.id)
        resolved, conflicts = resolve_line_items(li, li_b, _INCOME_FIELDS, adapter.tier, secondary_tier)
        for field, value in resolved.items():
            setattr(income, field, value)
        db.add(income)
        if conflicts:
            _record_conflicts(db, "income_statements", fp.id, conflicts, source, secondary_source)

        balance = fp.balance_sheet or BalanceSheet(financial_period_id=fp.id)
        resolved, conflicts = resolve_line_items(li, li_b, _BALANCE_FIELDS, adapter.tier, secondary_tier)
        for field, value in resolved.items():
            setattr(balance, field, value)
        db.add(balance)
        if conflicts:
            _record_conflicts(db, "balance_sheets", fp.id, conflicts, source, secondary_source)

        cash_flow = fp.cash_flow or CashFlow(financial_period_id=fp.id)
        resolved, conflicts = resolve_line_items(li, li_b, _CASH_FLOW_FIELDS, adapter.tier, secondary_tier)
        for field, value in resolved.items():
            setattr(cash_flow, field, value)
        db.add(cash_flow)
        if conflicts:
            _record_conflicts(db, "cash_flows", fp.id, conflicts, source, secondary_source)

        shares = fp.shares or Shares(financial_period_id=fp.id)
        resolved, conflicts = resolve_line_items(li, li_b, _SHARES_FIELDS, adapter.tier, secondary_tier)
        for field, value in resolved.items():
            setattr(shares, field, value)
        db.add(shares)
        if conflicts:
            _record_conflicts(db, "shares", fp.id, conflicts, source, secondary_source)

    # AUDIT FIX (final master pass, §21) — the fourth "engine built, never wired" defect.
    # `ProviderAdapter.get_estimates()` is implemented by FMP and by the demo adapter,
    # `ProviderEstimateRow` exists, and the `estimates` table exists with a unique constraint on
    # (security_id, period_end, metric, as_of_date). Nothing ever called it, nothing ever wrote a
    # row, and `build_snapshot_from_db()` never passed `forward_eps_estimate` — so it was None on
    # every real run and `forward_pe` and `eps_growth_forward` were PERMANENTLY NULL. `forward_pe`
    # is a member of the Valuation pillar, so every Valuation score was computed from six of its
    # seven metrics with nothing saying so.
    # Writes are idempotent against that unique constraint: an estimate already stored for the
    # same (period_end, metric, as_of_date) is updated in place, never duplicated on re-ingestion.
    estimates_written = _ingest_estimates(db, adapter, security.id, ticker, source)
    prices_written = _ingest_prices(db, adapter, security.id, ticker, source)

    db.commit()
    logger.info("ingest.security.complete", ticker=ticker, provider=adapter.name, is_demo=is_demo,
                periods=len(income_periods), estimates=estimates_written,
                prices=prices_written,
                secondary_provider=(secondary_adapter.name if secondary_adapter else None))
    return security.id


def _ingest_estimates(db: Session, adapter: ProviderAdapter, security_id: str, ticker: str,
                      source: Source) -> int:
    """Fetch and persist forward analyst estimates. Returns how many rows were written or updated.

    Never raises for an adapter that has not implemented estimates (EODHD's is
    `NotImplementedError`) — that degrades to "no estimates this run", which is exactly what
    `select_forward_estimate()` is built to report honestly, rather than crashing an ingestion
    that otherwise succeeded.
    """
    try:
        rows = adapter.get_estimates(ticker)
    except NotImplementedError:
        logger.warning("ingest.estimates.not_implemented", ticker=ticker, provider=adapter.name)
        return 0

    written = 0
    for r in rows:
        if r.consensus_value is None or r.period_end is None:
            continue
        existing = db.query(Estimate).filter_by(
            security_id=security_id, period_end=r.period_end, metric=r.metric,
            as_of_date=r.as_of_date,
        ).one_or_none()
        if existing is None:
            existing = Estimate(
                security_id=security_id, period_end=r.period_end, metric=r.metric,

=== 5. EXACT VALIDATION CONTRACT ===

=== 6. EXACT RESOLVER CONTRACT ===

=== 7. GUARD CONDITIONS ===
INGEST_SHA=00515c566771560e86dc9cb2e61fc48ac070a50d3c5e8e425a99a22ad34e059b
RESOLVER_SHA=670994146a74c132be4ee08bf8c50627c78d911a7239469767a826c6e5337e96
VALIDATION_SHA=486737385fd5ee3a3863fc04f385b3ce4d2c462938f914c2f21170e9999929f1

=== 8. VERIFY ACTUAL DEFECT BEFORE MUTATION ===

=== 9. BACKUP SOURCE FILES ===
Backups created.

=== 10. APPLY SL-004 FIX — GUARDED ===
GUARD FAIL: dual-source ingest block not found
No ingest mutation performed.
PATCH_APPLY=FAIL

=== 11. POST-PATCH SOURCE DIFF ===

=== 12. PYTHON SYNTAX CHECK ===
SKIPPED

=== 13. TARGETED SL-004 REGRESSION ===
SKIPPED

=== 14. EXISTING PROVIDER/VALIDATION TESTS ===
SKIPPED

=== 15. FINAL HASHES ===
670994146a74c132be4ee08bf8c50627c78d911a7239469767a826c6e5337e96  /data/files/bmw/stocklab-release-candidate-sl003-final-20260910-111453/app/adapters/resolver.py
486737385fd5ee3a3863fc04f385b3ce4d2c462938f914c2f21170e9999929f1  /data/files/bmw/stocklab-release-candidate-sl003-final-20260910-111453/app/adapters/validation.py
00515c566771560e86dc9cb2e61fc48ac070a50d3c5e8e425a99a22ad34e059b  /data/files/bmw/stocklab-release-candidate-sl003-final-20260910-111453/app/workers/ingest.py

=== 16. VERIFY PRODUCTION WAS NOT TOUCHED ===
stocklab-redis	docker.io/library/redis:7-alpine	Up 33 hours (healthy)
stocklab-db	docker.io/library/postgres:16-alpine	Up 32 hours (healthy)
stocklab-release-qa-redis	docker.io/library/redis:7	Up 26 hours
stocklab-frontend	localhost/stocklab-frontend:release-qa-20260909-182234	Up 24 hours (healthy)
stocklab-backend	localhost/stocklab-backend:screener-percent-final-20260909-194639	Up 21 hours (healthy)
stocklab-worker	localhost/stocklab-backend:screener-percent-final-20260909-194639	Up 21 hours (healthy)
stocklab-beat	localhost/stocklab-backend:screener-percent-final-20260909-194639	Up 21 hours

Production ports:
LISTEN 0      2048       127.0.0.1:18000      0.0.0.0:*    users:(("uvicorn",pid=1574355,fd=11))                                                                                                                                                             
LISTEN 0      511        127.0.0.1:6379       0.0.0.0:*    users:(("redis-server",pid=1133711,fd=6))                                                                                                                                                         
LISTEN 0      200        127.0.0.1:5432       0.0.0.0:*    users:(("postgres",pid=1141225,fd=6))                                                                                                                                                             
LISTEN 0      511        127.0.0.1:13000      0.0.0.0:*    users:(("next-server (v1",pid=1497838,fd=18))                                                                                                                                                     

Production restart counters:
stocklab-backend restart=0
stocklab-worker restart=0
stocklab-beat restart=0
stocklab-db restart=0
stocklab-redis restart=0
stocklab-frontend restart=0

============================================================
 FINAL RESULT
============================================================
SL004_PATCH=NOT_APPLIED
PRODUCTION=UNCHANGED

No source mutation was considered valid.

LOG=/data/files/bmw/stocklab-sl004-patch-20260910-180740.log
============================================================
